2022-04-22 13:05:56 +10:00
|
|
|
/* eslint-disable no-console */
|
|
|
|
/* eslint-disable import/order */
|
2022-07-29 14:57:40 +10:00
|
|
|
|
2022-04-22 13:05:56 +10:00
|
|
|
import Web3 from 'web3'
|
2022-07-14 19:31:37 +00:00
|
|
|
import { utils } from 'ethers'
|
2022-04-22 13:05:56 +10:00
|
|
|
import { ToastProgrammatic as Toast } from 'buefy'
|
|
|
|
|
|
|
|
import networkConfig from '@/networkConfig'
|
|
|
|
|
2022-07-14 19:31:37 +00:00
|
|
|
import GovernanceABI from '@/abis/Governance.abi.json'
|
2022-04-22 13:05:56 +10:00
|
|
|
import AggregatorABI from '@/abis/Aggregator.abi.json'
|
|
|
|
|
2022-11-24 08:43:32 +00:00
|
|
|
import { httpConfig } from '@/constants'
|
|
|
|
|
2022-04-22 13:05:56 +10:00
|
|
|
const { numberToHex, toWei, fromWei, toBN, hexToNumber, hexToNumberString } = require('web3-utils')
|
|
|
|
|
|
|
|
const state = () => {
|
|
|
|
return {
|
|
|
|
approvalAmount: 'unlimited',
|
|
|
|
lockedBalance: '0',
|
|
|
|
isFetchingLockedBalance: false,
|
|
|
|
currentDelegate: '0x0000000000000000000000000000000000000000',
|
|
|
|
timestamp: 0,
|
|
|
|
delegatedBalance: '0',
|
|
|
|
isFetchingDelegatedBalance: false,
|
|
|
|
delegators: [],
|
|
|
|
latestProposalId: {
|
|
|
|
value: null,
|
|
|
|
status: null
|
|
|
|
},
|
|
|
|
isFetchingProposals: true,
|
2022-07-29 14:57:40 +10:00
|
|
|
isCastingVote: false,
|
2022-04-22 13:05:56 +10:00
|
|
|
proposals: [],
|
|
|
|
voterReceipts: [],
|
|
|
|
hasActiveProposals: false,
|
|
|
|
constants: {
|
|
|
|
EXECUTION_DELAY: 172800,
|
|
|
|
EXECUTION_EXPIRATION: 259200,
|
|
|
|
PROPOSAL_THRESHOLD: '1000000000000000000000',
|
2023-12-19 17:39:47 +00:00
|
|
|
QUORUM_VOTES: '100000000000000000000000',
|
2022-04-22 13:05:56 +10:00
|
|
|
VOTING_PERIOD: 432000
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getters = {
|
2022-07-14 19:31:37 +00:00
|
|
|
getConfig: (state, getters, rootState) => ({ netId }) => {
|
|
|
|
return networkConfig[`netId${netId}`]
|
|
|
|
},
|
|
|
|
getWeb3: (state, getters, rootState) => ({ netId }) => {
|
2022-04-22 13:05:56 +10:00
|
|
|
const { url } = rootState.settings[`netId${netId}`].rpc
|
2022-11-24 08:43:32 +00:00
|
|
|
const httpProvider = new Web3.providers.HttpProvider(url, httpConfig)
|
|
|
|
|
|
|
|
return new Web3(httpProvider)
|
2022-07-14 19:31:37 +00:00
|
|
|
},
|
|
|
|
govContract: (state, getters, rootState) => ({ netId }) => {
|
|
|
|
const config = getters.getConfig({ netId })
|
2022-04-22 13:05:56 +10:00
|
|
|
const address = config['governance.contract.tornadocash.eth']
|
|
|
|
if (address) {
|
2022-07-14 19:31:37 +00:00
|
|
|
const web3 = getters.getWeb3({ netId })
|
|
|
|
const contract = new web3.eth.Contract(GovernanceABI, address)
|
|
|
|
return contract
|
2022-04-22 13:05:56 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
|
|
|
},
|
|
|
|
aggregatorContract: (state, getters, rootState, rootGetters) => {
|
|
|
|
const { aggregatorContract } = rootGetters['metamask/networkConfig']
|
|
|
|
const { url } = rootGetters['settings/currentRpc']
|
|
|
|
const web3 = new Web3(url)
|
|
|
|
return new web3.eth.Contract(AggregatorABI, aggregatorContract)
|
|
|
|
},
|
|
|
|
isFetchingProposals: ({ proposals, isFetchingProposals }) => {
|
|
|
|
if (proposals && proposals.length && !isFetchingProposals) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return isFetchingProposals
|
|
|
|
},
|
|
|
|
votingPower: (state) => {
|
|
|
|
return toBN(state.lockedBalance)
|
|
|
|
.add(toBN(state.delegatedBalance))
|
|
|
|
.toString(10)
|
|
|
|
},
|
|
|
|
quorumVotes: (state, getters, rootState, rootGetters) => {
|
|
|
|
return rootGetters['token/toDecimals'](state.constants.QUORUM_VOTES, 18)
|
|
|
|
},
|
|
|
|
votingPeriod: (state) => {
|
|
|
|
return toBN(state.constants.VOTING_PERIOD)
|
|
|
|
.divRound(toBN(24 * 60 * 60))
|
|
|
|
.toNumber()
|
|
|
|
},
|
|
|
|
isEnabledGovernance: (state, getters, rootState, rootGetters) => {
|
|
|
|
return Boolean(rootGetters['metamask/networkConfig']['governance.contract.tornadocash.eth'])
|
|
|
|
},
|
|
|
|
constants: (state) => {
|
|
|
|
return state.constants
|
|
|
|
},
|
|
|
|
isFetchingBalances: (state) => {
|
|
|
|
return state.isFetchingLockedBalance || state.isFetchingDelegatedBalance
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const mutations = {
|
|
|
|
SET_APPROVAL_AMOUNT(state, { approvalAmount }) {
|
|
|
|
state.approvalAmount = approvalAmount
|
|
|
|
},
|
|
|
|
SAVE_FETCHING_PROPOSALS(state, status) {
|
|
|
|
this._vm.$set(state, 'isFetchingProposals', status)
|
|
|
|
},
|
2022-07-29 14:57:40 +10:00
|
|
|
SAVE_CASTING_VOTE(state, status) {
|
|
|
|
this._vm.$set(state, 'isCastingVote', status)
|
2022-07-14 19:31:37 +00:00
|
|
|
},
|
2022-04-22 13:05:56 +10:00
|
|
|
SAVE_LOCKED_BALANCE(state, { balance }) {
|
|
|
|
this._vm.$set(state, 'lockedBalance', balance)
|
|
|
|
},
|
|
|
|
SAVE_FETCHING_LOCKED_BALANCE(state, status) {
|
|
|
|
this._vm.$set(state, 'isFetchingLockedBalance', status)
|
|
|
|
},
|
|
|
|
SAVE_LOCKED_TIMESTAMP(state, { timestamp }) {
|
|
|
|
this._vm.$set(state, 'timestamp', timestamp)
|
|
|
|
},
|
|
|
|
SAVE_LATEST_PROPOSAL_ID(state, { id, status }) {
|
|
|
|
this._vm.$set(state, 'latestProposalId', { value: id, status })
|
|
|
|
},
|
|
|
|
SAVE_DELEGATEE(state, { currentDelegate }) {
|
|
|
|
this._vm.$set(state, 'currentDelegate', currentDelegate)
|
|
|
|
},
|
|
|
|
SAVE_PROPOSALS(state, proposals) {
|
|
|
|
this._vm.$set(state, 'proposals', proposals)
|
|
|
|
},
|
|
|
|
SAVE_VOTER_RECEIPT(state, { hasVoted, support, balance, id }) {
|
|
|
|
this._vm.$set(state.voterReceipts, id, { hasVoted, support, balance })
|
|
|
|
},
|
|
|
|
SAVE_DELEGATED_BALANCE(state, balance) {
|
|
|
|
this._vm.$set(state, 'delegatedBalance', balance)
|
|
|
|
},
|
|
|
|
SAVE_FETCHING_DELEGATED_BALANCE(state, status) {
|
|
|
|
this._vm.$set(state, 'isFetchingDelegatedBalance', status)
|
|
|
|
},
|
|
|
|
SAVE_DELEGATORS(state, uniq) {
|
|
|
|
this._vm.$set(state, 'delegators', uniq)
|
|
|
|
},
|
|
|
|
SET_HAS_ACTIVE_PROPOSALS(state, condition) {
|
|
|
|
this._vm.$set(state, 'hasActiveProposals', condition)
|
|
|
|
},
|
|
|
|
SAVE_CONSTANTS(state, constants) {
|
|
|
|
this._vm.$set(state, 'constants', constants)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// enum ProposalState { Pending, Active, Defeated, Timelocked, AwaitingExecution, Executed, Expired }
|
|
|
|
|
|
|
|
const ProposalState = [
|
|
|
|
'pending',
|
|
|
|
'active',
|
|
|
|
'defeated',
|
|
|
|
'timeLocked',
|
|
|
|
'awaitingExecution',
|
|
|
|
'executed',
|
|
|
|
'expired'
|
|
|
|
]
|
|
|
|
|
|
|
|
const proposalIntervalConstants = [
|
|
|
|
// 'CLOSING_PERIOD',
|
|
|
|
'EXECUTION_DELAY',
|
|
|
|
'EXECUTION_EXPIRATION',
|
|
|
|
// 'VOTE_EXTEND_TIME',
|
|
|
|
// 'VOTING_DELAY',
|
|
|
|
'VOTING_PERIOD'
|
|
|
|
]
|
2022-07-14 19:31:37 +00:00
|
|
|
|
2022-04-22 13:05:56 +10:00
|
|
|
const govConstants = ['PROPOSAL_THRESHOLD', 'QUORUM_VOTES']
|
|
|
|
|
|
|
|
const actions = {
|
|
|
|
async createProposal(
|
|
|
|
{ getters, rootGetters, state, commit, rootState, dispatch },
|
|
|
|
{ proposalAddress, title, description }
|
|
|
|
) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
2023-04-16 02:36:20 +00:00
|
|
|
const { lockedBalance, constants, delegators } = state
|
2022-04-22 13:05:56 +10:00
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
|
2023-03-23 03:03:21 +00:00
|
|
|
const proposalThreshold = toBN(constants.PROPOSAL_THRESHOLD)
|
2023-04-16 02:36:20 +00:00
|
|
|
const proposeIndependently = toBN(lockedBalance).gte(proposalThreshold)
|
2023-03-23 03:03:21 +00:00
|
|
|
|
2022-04-22 13:05:56 +10:00
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const json = JSON.stringify({ title, description })
|
2023-04-16 02:36:20 +00:00
|
|
|
const delegatorAddress = delegators[delegators.length - 1]
|
|
|
|
|
|
|
|
let data, gas
|
|
|
|
|
|
|
|
if (proposeIndependently) {
|
|
|
|
data = await govInstance.methods.propose(proposalAddress, json).encodeABI()
|
|
|
|
gas = await govInstance.methods.propose(proposalAddress, json).estimateGas({
|
|
|
|
from: ethAccount,
|
|
|
|
value: 0
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
data = await govInstance.methods
|
|
|
|
.proposeByDelegate(delegatorAddress, proposalAddress, json)
|
|
|
|
.encodeABI()
|
|
|
|
gas = await govInstance.methods
|
|
|
|
.proposeByDelegate(delegatorAddress, proposalAddress, json)
|
|
|
|
.estimateGas({
|
|
|
|
from: ethAccount,
|
|
|
|
value: 0
|
|
|
|
})
|
|
|
|
}
|
2022-04-22 13:05:56 +10:00
|
|
|
|
|
|
|
const callParams = {
|
|
|
|
method: 'eth_sendTransaction',
|
|
|
|
params: {
|
|
|
|
to: govInstance._address,
|
|
|
|
gas: numberToHex(gas + 100000),
|
|
|
|
data
|
|
|
|
},
|
|
|
|
watcherParams: {
|
|
|
|
title: 'creatingProposal',
|
|
|
|
successTitle: 'proposalCreated',
|
|
|
|
storeType: 'govTxs',
|
|
|
|
onSuccess: () => {
|
|
|
|
dispatch('torn/fetchTokenBalance', {}, { root: true })
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isSaving: false
|
|
|
|
}
|
|
|
|
|
|
|
|
const txHash = await dispatch('metamask/sendTransaction', callParams, { root: true })
|
|
|
|
|
|
|
|
commit(
|
|
|
|
'txHashKeeper/SAVE_TX_HASH',
|
|
|
|
{
|
|
|
|
txHash,
|
|
|
|
storeType: 'govTxs',
|
|
|
|
type: 'Proposal',
|
|
|
|
netId
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
|
|
|
|
await dispatch('fetchProposals', { requestId: state.proposals.length })
|
|
|
|
|
|
|
|
this.$router.push('/governance')
|
|
|
|
} catch (e) {
|
|
|
|
console.error('createProposal', e.message)
|
|
|
|
dispatch(
|
|
|
|
'notice/addNoticeWithInterval',
|
|
|
|
{
|
|
|
|
notice: {
|
|
|
|
title: 'internalError',
|
|
|
|
type: 'danger'
|
|
|
|
},
|
|
|
|
interval: 3000
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async lock({ getters, rootGetters, commit, rootState, dispatch }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
const { deadline, v, r, s } = rootState.torn.signature
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const amount = toWei(rootState.torn.signature.amount.toString())
|
|
|
|
const gas = await govInstance.methods
|
|
|
|
.lock(ethAccount, amount, deadline, v, r, s)
|
|
|
|
.estimateGas({ from: ethAccount, value: 0 })
|
|
|
|
const data = await govInstance.methods.lock(ethAccount, amount, deadline, v, r, s).encodeABI()
|
|
|
|
|
|
|
|
const callParams = {
|
|
|
|
method: 'eth_sendTransaction',
|
|
|
|
params: {
|
|
|
|
to: govInstance._address,
|
|
|
|
gas: numberToHex(gas + 30000),
|
|
|
|
data
|
|
|
|
},
|
|
|
|
watcherParams: {
|
|
|
|
title: 'locking',
|
|
|
|
successTitle: 'lockedNotice',
|
|
|
|
storeType: 'govTxs',
|
|
|
|
onSuccess: () => {
|
|
|
|
dispatch('fetchBalances')
|
|
|
|
dispatch('torn/fetchTokenBalance', {}, { root: true })
|
|
|
|
commit('torn/REMOVE_SIGNATURE', {}, { root: true })
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isAwait: false
|
|
|
|
}
|
|
|
|
|
|
|
|
const txHash = await dispatch('metamask/sendTransaction', callParams, { root: true })
|
|
|
|
|
|
|
|
commit(
|
|
|
|
'txHashKeeper/SAVE_TX_HASH',
|
|
|
|
{
|
|
|
|
txHash,
|
|
|
|
storeType: 'govTxs',
|
|
|
|
type: 'Lock',
|
|
|
|
netId
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
console.error('lock', e.message)
|
|
|
|
dispatch(
|
|
|
|
'notice/addNoticeWithInterval',
|
|
|
|
{
|
|
|
|
notice: {
|
|
|
|
title: 'internalError',
|
|
|
|
type: 'danger'
|
|
|
|
},
|
|
|
|
interval: 3000
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async lockWithApproval({ getters, rootGetters, commit, rootState, dispatch }, { amount }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
amount = toWei(amount.toString())
|
|
|
|
const gas = await govInstance.methods
|
|
|
|
.lockWithApproval(amount)
|
|
|
|
.estimateGas({ from: ethAccount, value: 0 })
|
|
|
|
const data = await govInstance.methods.lockWithApproval(amount).encodeABI()
|
|
|
|
|
|
|
|
const callParams = {
|
|
|
|
method: 'eth_sendTransaction',
|
|
|
|
params: {
|
|
|
|
to: govInstance._address,
|
|
|
|
gas: numberToHex(gas + 100000),
|
|
|
|
data
|
|
|
|
},
|
|
|
|
watcherParams: {
|
|
|
|
title: 'locking',
|
|
|
|
successTitle: 'lockedNotice',
|
|
|
|
storeType: 'govTxs',
|
|
|
|
onSuccess: () => {
|
|
|
|
dispatch('fetchBalances')
|
|
|
|
dispatch('torn/fetchTokenBalance', {}, { root: true })
|
|
|
|
dispatch('torn/fetchTokenAllowance', {}, { root: true })
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isAwait: false
|
|
|
|
}
|
|
|
|
|
|
|
|
const txHash = await dispatch('metamask/sendTransaction', callParams, { root: true })
|
|
|
|
|
|
|
|
commit(
|
|
|
|
'txHashKeeper/SAVE_TX_HASH',
|
|
|
|
{
|
|
|
|
txHash,
|
|
|
|
storeType: 'govTxs',
|
|
|
|
type: 'Lock',
|
|
|
|
netId
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
console.error('lockWithApproval', e.message)
|
|
|
|
Toast.open({
|
|
|
|
message: this.app.i18n.t('internalError'),
|
|
|
|
type: 'is-danger',
|
|
|
|
duration: 3000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
2022-07-14 19:31:37 +00:00
|
|
|
async castVote(context, payload) {
|
|
|
|
const { getters, rootGetters, commit, rootState, dispatch, state } = context
|
|
|
|
const { id, support, contact = '', message = '' } = payload
|
|
|
|
|
2022-07-29 14:57:40 +10:00
|
|
|
commit('SAVE_CASTING_VOTE', true)
|
2022-07-14 19:31:37 +00:00
|
|
|
|
2022-04-22 13:05:56 +10:00
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const delegators = [...state.delegators]
|
2022-07-14 19:31:37 +00:00
|
|
|
const web3 = getters.getWeb3({ netId })
|
2022-04-22 13:05:56 +10:00
|
|
|
|
|
|
|
if (toBN(state.lockedBalance).gt(toBN('0'))) {
|
|
|
|
delegators.push(ethAccount)
|
|
|
|
}
|
|
|
|
|
2022-07-14 19:31:37 +00:00
|
|
|
const data = govInstance.methods.castDelegatedVote(delegators, id, support).encodeABI()
|
|
|
|
let dataWithTail = data
|
|
|
|
|
|
|
|
if (contact || message) {
|
|
|
|
const value = JSON.stringify([contact, message])
|
|
|
|
const tail = utils.defaultAbiCoder.encode(['string'], [value])
|
|
|
|
dataWithTail = utils.hexConcat([data, tail])
|
|
|
|
}
|
|
|
|
|
|
|
|
const gas = await web3.eth.estimateGas({
|
|
|
|
from: ethAccount,
|
|
|
|
to: govInstance._address,
|
|
|
|
value: 0,
|
|
|
|
data: dataWithTail
|
|
|
|
})
|
2022-04-22 13:05:56 +10:00
|
|
|
|
|
|
|
const callParams = {
|
|
|
|
method: 'eth_sendTransaction',
|
|
|
|
params: {
|
|
|
|
to: govInstance._address,
|
|
|
|
gas: numberToHex(gas + 30000),
|
2022-07-14 19:31:37 +00:00
|
|
|
data: dataWithTail
|
2022-04-22 13:05:56 +10:00
|
|
|
},
|
|
|
|
watcherParams: {
|
|
|
|
title: support ? 'votingFor' : 'votingAgainst',
|
|
|
|
successTitle: support ? 'votedFor' : 'votedAgainst',
|
|
|
|
storeType: 'govTxs',
|
|
|
|
onSuccess: () => {
|
|
|
|
dispatch('fetchProposals', { requestId: id })
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isAwait: false
|
|
|
|
}
|
|
|
|
|
|
|
|
const txHash = await dispatch('metamask/sendTransaction', callParams, { root: true })
|
|
|
|
|
|
|
|
commit(
|
|
|
|
'txHashKeeper/SAVE_TX_HASH',
|
|
|
|
{
|
|
|
|
txHash,
|
|
|
|
storeType: 'govTxs',
|
|
|
|
type: 'CastVote',
|
|
|
|
netId
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
console.error('castVote', e.message)
|
|
|
|
dispatch(
|
|
|
|
'notice/addNoticeWithInterval',
|
|
|
|
{
|
|
|
|
notice: {
|
|
|
|
title: 'internalError',
|
|
|
|
type: 'danger'
|
|
|
|
},
|
|
|
|
interval: 3000
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
} finally {
|
|
|
|
dispatch('loading/disable', {}, { root: true })
|
2022-07-29 14:57:40 +10:00
|
|
|
commit('SAVE_CASTING_VOTE', false)
|
2022-04-22 13:05:56 +10:00
|
|
|
}
|
|
|
|
},
|
|
|
|
async executeProposal({ getters, rootGetters, commit, rootState, dispatch }, { id }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const gas = await govInstance.methods.execute(id).estimateGas({ from: ethAccount, value: 0 })
|
|
|
|
const data = await govInstance.methods.execute(id).encodeABI()
|
|
|
|
|
|
|
|
const callParams = {
|
|
|
|
method: 'eth_sendTransaction',
|
|
|
|
params: {
|
|
|
|
to: govInstance._address,
|
|
|
|
gas: numberToHex(gas + 100000),
|
|
|
|
data
|
|
|
|
},
|
|
|
|
watcherParams: {
|
|
|
|
title: 'executingProposal',
|
|
|
|
successTitle: 'proposalExecuted',
|
|
|
|
storeType: 'govTxs',
|
|
|
|
onSuccess: () => {
|
|
|
|
dispatch('fetchProposals', { requestId: id })
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isAwait: false
|
|
|
|
}
|
|
|
|
|
|
|
|
const txHash = await dispatch('metamask/sendTransaction', callParams, { root: true })
|
|
|
|
|
|
|
|
commit(
|
|
|
|
'txHashKeeper/SAVE_TX_HASH',
|
|
|
|
{
|
|
|
|
txHash,
|
|
|
|
storeType: 'govTxs',
|
|
|
|
type: 'ExecuteProposal',
|
|
|
|
netId
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
console.error('executeProposal', e.message)
|
|
|
|
dispatch(
|
|
|
|
'notice/addNoticeWithInterval',
|
|
|
|
{
|
|
|
|
notice: {
|
|
|
|
title: 'internalError',
|
|
|
|
type: 'danger'
|
|
|
|
},
|
|
|
|
interval: 3000
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async unlock({ getters, rootGetters, commit, rootState, dispatch }, { amount }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
amount = toWei(amount.toString())
|
|
|
|
const gas = await govInstance.methods.unlock(amount).estimateGas({ from: ethAccount, value: 0 })
|
|
|
|
const data = await govInstance.methods.unlock(amount).encodeABI()
|
|
|
|
|
|
|
|
const callParams = {
|
|
|
|
method: 'eth_sendTransaction',
|
|
|
|
params: {
|
|
|
|
to: govInstance._address,
|
|
|
|
gas: numberToHex(gas + 100000),
|
|
|
|
data
|
|
|
|
},
|
|
|
|
watcherParams: {
|
|
|
|
title: 'unlocking',
|
|
|
|
successTitle: 'unlocked',
|
|
|
|
storeType: 'govTxs',
|
|
|
|
onSuccess: () => {
|
|
|
|
dispatch('fetchBalances')
|
|
|
|
dispatch('torn/fetchTokenBalance', {}, { root: true })
|
|
|
|
commit('torn/REMOVE_SIGNATURE', {}, { root: true })
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isAwait: false
|
|
|
|
}
|
|
|
|
|
|
|
|
const txHash = await dispatch('metamask/sendTransaction', callParams, { root: true })
|
|
|
|
|
|
|
|
commit(
|
|
|
|
'txHashKeeper/SAVE_TX_HASH',
|
|
|
|
{
|
|
|
|
txHash,
|
|
|
|
storeType: 'govTxs',
|
|
|
|
type: 'Unlock',
|
|
|
|
netId
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
console.error('unlock', e.message)
|
|
|
|
dispatch(
|
|
|
|
'notice/addNoticeWithInterval',
|
|
|
|
{
|
|
|
|
notice: {
|
|
|
|
title: 'internalError',
|
|
|
|
type: 'danger'
|
|
|
|
},
|
|
|
|
interval: 3000
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async delegate({ getters, rootGetters, commit, rootState, dispatch }, { delegatee }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const gas = await govInstance.methods.delegate(delegatee).estimateGas({ from: ethAccount, value: 0 })
|
|
|
|
const data = await govInstance.methods.delegate(delegatee).encodeABI()
|
|
|
|
|
|
|
|
const callParams = {
|
|
|
|
method: 'eth_sendTransaction',
|
|
|
|
params: {
|
|
|
|
to: govInstance._address,
|
|
|
|
gas: numberToHex(gas + 100000),
|
|
|
|
data
|
|
|
|
},
|
|
|
|
watcherParams: {
|
|
|
|
title: 'delegating',
|
|
|
|
successTitle: 'delegated',
|
|
|
|
storeType: 'govTxs',
|
|
|
|
onSuccess: () => {
|
|
|
|
dispatch('fetchDelegatee')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isAwait: false
|
|
|
|
}
|
|
|
|
|
|
|
|
const txHash = await dispatch('metamask/sendTransaction', callParams, { root: true })
|
|
|
|
|
|
|
|
commit(
|
|
|
|
'txHashKeeper/SAVE_TX_HASH',
|
|
|
|
{
|
|
|
|
storeType: 'govTxs',
|
|
|
|
txHash,
|
|
|
|
type: 'Delegate',
|
|
|
|
netId
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
console.error('delegate', e.message)
|
|
|
|
dispatch(
|
|
|
|
'notice/addNoticeWithInterval',
|
|
|
|
{
|
|
|
|
notice: {
|
|
|
|
title: 'internalError',
|
|
|
|
type: 'danger'
|
|
|
|
},
|
|
|
|
interval: 3000
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async undelegate({ getters, rootGetters, commit, rootState, dispatch }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const gas = await govInstance.methods.undelegate().estimateGas({ from: ethAccount, value: 0 })
|
|
|
|
const data = await govInstance.methods.undelegate().encodeABI()
|
|
|
|
|
|
|
|
const callParams = {
|
|
|
|
method: 'eth_sendTransaction',
|
|
|
|
params: {
|
|
|
|
to: govInstance._address,
|
|
|
|
gas: numberToHex(gas + 100000),
|
|
|
|
data
|
|
|
|
},
|
|
|
|
watcherParams: {
|
|
|
|
title: 'undelegating',
|
|
|
|
successTitle: 'undelegated',
|
|
|
|
storeType: 'govTxs',
|
|
|
|
onSuccess: () => {
|
|
|
|
dispatch('fetchDelegatee')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isAwait: false
|
|
|
|
}
|
|
|
|
|
|
|
|
const txHash = await dispatch('metamask/sendTransaction', callParams, { root: true })
|
|
|
|
|
|
|
|
commit(
|
|
|
|
'txHashKeeper/SAVE_TX_HASH',
|
|
|
|
{
|
|
|
|
txHash,
|
|
|
|
storeType: 'govTxs',
|
|
|
|
type: 'Undelegate',
|
|
|
|
netId
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
console.error('undelegate', e.message)
|
|
|
|
dispatch(
|
|
|
|
'notice/addNoticeWithInterval',
|
|
|
|
{
|
|
|
|
notice: {
|
|
|
|
title: 'internalError',
|
|
|
|
type: 'danger'
|
|
|
|
},
|
|
|
|
interval: 3000
|
|
|
|
},
|
|
|
|
{ root: true }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async fetchProposals({ rootGetters, getters, commit }, { requestId }) {
|
2022-06-29 22:17:24 +10:00
|
|
|
let proposals = []
|
2022-04-22 13:05:56 +10:00
|
|
|
try {
|
|
|
|
commit('SAVE_FETCHING_PROPOSALS', true)
|
|
|
|
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
const aggregatorContract = getters.aggregatorContract
|
|
|
|
const govInstance = getters.govContract({ netId })
|
2022-07-14 19:31:37 +00:00
|
|
|
const config = getters.getConfig({ netId })
|
2022-04-22 13:05:56 +10:00
|
|
|
|
|
|
|
if (!govInstance) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-06-29 22:17:24 +10:00
|
|
|
const [events, statuses] = await Promise.all([
|
|
|
|
govInstance.getPastEvents('ProposalCreated', {
|
2022-07-29 14:57:40 +10:00
|
|
|
fromBlock: config.constants.GOVERNANCE_BLOCK,
|
2022-06-29 22:17:24 +10:00
|
|
|
toBlock: 'latest'
|
|
|
|
}),
|
|
|
|
aggregatorContract.methods.getAllProposals(govInstance._address).call()
|
|
|
|
])
|
2022-04-22 13:05:56 +10:00
|
|
|
|
2022-06-29 22:17:24 +10:00
|
|
|
const parseDescription = ({ id, text }) => {
|
2022-04-22 13:05:56 +10:00
|
|
|
if (netId === 1) {
|
2022-06-29 22:17:24 +10:00
|
|
|
switch (id) {
|
2022-04-22 13:05:56 +10:00
|
|
|
case 1:
|
2022-06-29 22:17:24 +10:00
|
|
|
return {
|
|
|
|
title: text,
|
|
|
|
description: 'See: https://torn.community/t/proposal-1-enable-torn-transfers/38'
|
|
|
|
}
|
2022-04-22 13:05:56 +10:00
|
|
|
case 10:
|
2022-06-29 22:17:24 +10:00
|
|
|
text = text.replace('\n', '\\n\\n')
|
2022-04-22 13:05:56 +10:00
|
|
|
break
|
|
|
|
case 11:
|
2022-06-29 22:17:24 +10:00
|
|
|
text = text.replace('"description"', ',"description"')
|
|
|
|
break
|
|
|
|
case 13:
|
|
|
|
text = text.replace(/\\\\n\\\\n(\s)?(\\n)?/g, '\\n')
|
2023-06-08 23:52:55 -07:00
|
|
|
break
|
|
|
|
// Fix invalid JSON in proposal 15: replace single quotes with double and add comma before description
|
|
|
|
case 15:
|
|
|
|
text = text.replaceAll(`'`, `"`)
|
|
|
|
text = text.replace('"description"', ',"description"')
|
|
|
|
break
|
|
|
|
case 16:
|
|
|
|
text = text.replace('#16: ', '')
|
|
|
|
break
|
|
|
|
// Add title to empty (without title and description) hacker proposal 21
|
|
|
|
case 21:
|
|
|
|
return {
|
|
|
|
title: 'Proposal #21: Restore Governance',
|
|
|
|
description: ''
|
|
|
|
}
|
2022-04-22 13:05:56 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-29 22:17:24 +10:00
|
|
|
let title, description, rest
|
|
|
|
try {
|
|
|
|
;({ title, description } = JSON.parse(text))
|
|
|
|
} catch {
|
|
|
|
;[title, ...rest] = text.split('\n', 2)
|
|
|
|
description = rest.join('\n')
|
2022-04-22 13:05:56 +10:00
|
|
|
}
|
|
|
|
|
2022-06-29 22:17:24 +10:00
|
|
|
return { title, description }
|
|
|
|
}
|
2022-04-22 13:05:56 +10:00
|
|
|
|
2022-06-29 22:17:24 +10:00
|
|
|
proposals = events
|
2022-07-14 19:31:37 +00:00
|
|
|
.map(({ returnValues, blockNumber }, index) => {
|
2022-06-29 22:17:24 +10:00
|
|
|
const id = Number(returnValues.id)
|
|
|
|
const { state, startTime, endTime, forVotes, againstVotes } = statuses[index]
|
|
|
|
const { title, description } = parseDescription({ id, text: returnValues.description })
|
|
|
|
|
|
|
|
return {
|
|
|
|
id,
|
|
|
|
title,
|
|
|
|
description,
|
|
|
|
target: returnValues.target,
|
|
|
|
proposer: returnValues.proposer,
|
|
|
|
endTime: Number(endTime),
|
|
|
|
startTime: Number(startTime),
|
|
|
|
status: ProposalState[Number(state)],
|
2022-07-14 19:31:37 +00:00
|
|
|
blockNumber,
|
2022-06-29 22:17:24 +10:00
|
|
|
results: {
|
|
|
|
for: fromWei(forVotes),
|
|
|
|
against: fromWei(againstVotes)
|
|
|
|
}
|
2022-04-22 13:05:56 +10:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.sort((a, b) => {
|
|
|
|
return a.id - b.id
|
|
|
|
})
|
2022-06-29 22:17:24 +10:00
|
|
|
|
2022-04-22 13:05:56 +10:00
|
|
|
if (requestId) {
|
|
|
|
return proposals[requestId]
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.error('fetchProposals', e.message)
|
|
|
|
} finally {
|
|
|
|
commit('SAVE_PROPOSALS', proposals)
|
|
|
|
commit('SAVE_FETCHING_PROPOSALS', false)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async fetchBalances({ getters, rootGetters, commit, rootState }) {
|
|
|
|
try {
|
|
|
|
commit('SAVE_FETCHING_LOCKED_BALANCE', true)
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
|
|
|
|
if (!ethAccount) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const balance = await govInstance.methods.lockedBalance(ethAccount).call()
|
|
|
|
commit('SAVE_LOCKED_BALANCE', { balance })
|
|
|
|
} catch (e) {
|
|
|
|
console.error('fetchBalances', e.message)
|
|
|
|
} finally {
|
|
|
|
commit('SAVE_FETCHING_LOCKED_BALANCE', false)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async fetchedLockedTimestamp({ getters, rootGetters, commit, rootState, dispatch }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
|
|
|
|
if (!ethAccount) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const timestamp = await govInstance.methods.canWithdrawAfter(ethAccount).call()
|
|
|
|
commit('SAVE_LOCKED_TIMESTAMP', { timestamp })
|
|
|
|
} catch (e) {
|
|
|
|
console.error('fetchedLockedTimestamp', e.message)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async fetchLatestProposalId({ getters, rootGetters, commit, rootState, state }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
|
|
|
|
if (!ethAccount) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const id = await govInstance.methods.latestProposalIds(ethAccount).call()
|
|
|
|
let status = null
|
|
|
|
if (Number(id)) {
|
|
|
|
status = await govInstance.methods.state(id).call()
|
|
|
|
status = Number(status) > 1 ? null : 'active'
|
|
|
|
}
|
|
|
|
commit('SAVE_LATEST_PROPOSAL_ID', { id, status })
|
|
|
|
console.log('status', state.latestProposalId)
|
|
|
|
} catch (e) {
|
|
|
|
console.error('fetchLatestProposalId', e.message)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async fetchDelegatedBalance({ getters, rootGetters, commit, rootState, dispatch }) {
|
|
|
|
try {
|
|
|
|
commit('SAVE_FETCHING_DELEGATED_BALANCE', true)
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
|
|
|
|
if (!ethAccount) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const netId = rootGetters['metamask/netId']
|
2022-07-14 19:31:37 +00:00
|
|
|
const config = getters.getConfig({ netId })
|
2022-04-22 13:05:56 +10:00
|
|
|
|
|
|
|
const aggregatorContract = getters.aggregatorContract
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
let delegatedAccs = await govInstance.getPastEvents('Delegated', {
|
|
|
|
filter: {
|
|
|
|
to: ethAccount
|
|
|
|
},
|
2022-07-29 14:57:40 +10:00
|
|
|
fromBlock: config.constants.GOVERNANCE_BLOCK,
|
2022-04-22 13:05:56 +10:00
|
|
|
toBlock: 'latest'
|
|
|
|
})
|
|
|
|
let undelegatedAccs = await govInstance.getPastEvents('Undelegated', {
|
|
|
|
filter: {
|
|
|
|
from: ethAccount
|
|
|
|
},
|
2022-07-29 14:57:40 +10:00
|
|
|
fromBlock: config.constants.GOVERNANCE_BLOCK,
|
2022-04-22 13:05:56 +10:00
|
|
|
toBlock: 'latest'
|
|
|
|
})
|
|
|
|
delegatedAccs = delegatedAccs.map((acc) => acc.returnValues.account)
|
|
|
|
undelegatedAccs = undelegatedAccs.map((acc) => acc.returnValues.account)
|
|
|
|
const uniq = delegatedAccs.filter((obj, index, self) => {
|
|
|
|
const indexUndelegated = undelegatedAccs.indexOf(obj)
|
|
|
|
if (indexUndelegated !== -1) {
|
|
|
|
undelegatedAccs.splice(indexUndelegated, 1)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
let balances = await aggregatorContract.methods.getGovernanceBalances(govInstance._address, uniq).call()
|
|
|
|
balances = balances.reduce((acc, balance, i) => {
|
|
|
|
acc = acc.add(toBN(balance))
|
|
|
|
return acc
|
|
|
|
}, toBN('0'))
|
|
|
|
commit('SAVE_DELEGATED_BALANCE', balances.toString(10))
|
|
|
|
commit('SAVE_DELEGATORS', uniq)
|
|
|
|
} catch (e) {
|
|
|
|
console.error('fetchDelegatedBalance', e.message)
|
|
|
|
} finally {
|
|
|
|
commit('SAVE_FETCHING_DELEGATED_BALANCE', false)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async fetchDelegatee({ getters, rootGetters, commit, rootState, dispatch }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
|
|
|
|
if (!ethAccount) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const currentDelegate = await govInstance.methods.delegatedTo(ethAccount).call()
|
|
|
|
console.log('currentDelegate', currentDelegate)
|
|
|
|
commit('SAVE_DELEGATEE', { currentDelegate })
|
|
|
|
} catch (e) {
|
|
|
|
console.error('fetchDelegatee', e.message)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async fetchReceipt({ getters, rootGetters, commit, rootState, dispatch }, { id }) {
|
|
|
|
try {
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
console.log('fetchReceipt', id)
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const [hasVoted, support, balance] = await govInstance.methods.getReceipt(id, ethAccount).call()
|
|
|
|
console.log('fetchReceipt', hasVoted, support, balance)
|
|
|
|
commit('SAVE_VOTER_RECEIPT', { hasVoted, support, balance, id })
|
|
|
|
} catch (e) {
|
|
|
|
console.error('fetchReceipt', e.message)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async fetchUserData({ getters, rootGetters, commit, rootState, dispatch }) {
|
|
|
|
try {
|
|
|
|
commit('SAVE_FETCHING_LOCKED_BALANCE', true)
|
|
|
|
const { ethAccount } = rootState.metamask
|
|
|
|
|
|
|
|
if (!ethAccount) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const aggregatorContract = getters.aggregatorContract
|
|
|
|
const {
|
|
|
|
balance,
|
|
|
|
latestProposalId,
|
|
|
|
timelock,
|
|
|
|
delegatee,
|
|
|
|
...userdata
|
|
|
|
} = await aggregatorContract.methods.getUserData(govInstance._address, ethAccount).call()
|
|
|
|
commit('SAVE_DELEGATEE', { currentDelegate: delegatee })
|
|
|
|
|
|
|
|
const latestProposalIdState = ProposalState[Number(userdata.latestProposalIdState)]
|
|
|
|
commit('SAVE_LATEST_PROPOSAL_ID', { id: Number(latestProposalId), status: latestProposalIdState })
|
|
|
|
commit('SAVE_LOCKED_TIMESTAMP', { timestamp: Number(timelock) })
|
|
|
|
commit('SAVE_LOCKED_BALANCE', { balance })
|
|
|
|
} catch (e) {
|
|
|
|
console.error('fetchUserData', e.message)
|
|
|
|
} finally {
|
|
|
|
commit('SAVE_FETCHING_LOCKED_BALANCE', false)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async checkActiveProposals({ getters, rootGetters, commit }) {
|
|
|
|
if (!getters.isEnabledGovernance) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const { 'governance.contract.tornadocash.eth': governanceAddress } = rootGetters['metamask/networkConfig']
|
|
|
|
const aggregatorContract = getters.aggregatorContract
|
|
|
|
|
|
|
|
const statuses = await aggregatorContract.methods.getAllProposals(governanceAddress).call()
|
|
|
|
let isActive = false
|
|
|
|
if (statuses && Array.isArray(statuses)) {
|
|
|
|
isActive = statuses.find((status) => Number(status.state) === 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
commit('SET_HAS_ACTIVE_PROPOSALS', Boolean(isActive))
|
|
|
|
},
|
|
|
|
async fetchConstants({ commit, getters, dispatch, rootGetters }) {
|
|
|
|
const netId = rootGetters['metamask/netId']
|
|
|
|
const govInstance = getters.govContract({ netId })
|
|
|
|
const constants = [].concat(govConstants, proposalIntervalConstants)
|
|
|
|
|
|
|
|
const params = constants.map((name) => {
|
|
|
|
return {
|
|
|
|
target: govInstance._address,
|
|
|
|
callData: govInstance.methods[name]().encodeABI()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const multicallArray = await dispatch('application/aggregateMulticall', { params }, { root: true })
|
|
|
|
|
|
|
|
if (multicallArray) {
|
|
|
|
const hexToNumberConverter = (acc, curr, index) => {
|
|
|
|
const name = constants[index]
|
|
|
|
const value = govConstants.includes(name) ? hexToNumberString(curr) : hexToNumber(curr)
|
|
|
|
|
|
|
|
return { ...acc, [name]: value }
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = multicallArray.reduce(hexToNumberConverter, {})
|
|
|
|
|
|
|
|
commit('SAVE_CONSTANTS', result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default {
|
|
|
|
namespaced: true,
|
|
|
|
state,
|
|
|
|
getters,
|
|
|
|
mutations,
|
|
|
|
actions
|
|
|
|
}
|