UNPKG

@dydxfoundation/governance

Version:
370 lines (369 loc) 19 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const ethers_1 = require("ethers"); const utils_1 = require("ethers/lib/utils"); const lodash_1 = require("lodash"); const types_1 = require("../../../types"); const config_1 = require("../config"); const types_2 = require("../types"); const GovernanceMethodTypes_1 = require("../types/GovernanceMethodTypes"); const governance_1 = require("../utils/governance"); const helpers_1 = require("../utils/helpers"); const ipfs_1 = require("../utils/ipfs"); const parsings_1 = require("../utils/parsings"); const subgraph_1 = require("../utils/subgraph"); const methodValidators_1 = require("../validators/methodValidators"); const paramValidators_1 = require("../validators/paramValidators"); const BaseService_1 = __importDefault(require("./BaseService")); class DydxGovernanceService extends BaseService_1.default { constructor(config, erc20Service, governanceTokenDelegationService, subgraphClient, governanceTokens, hardhatGovernanceAddresses) { super(config, types_1.DydxGovernor__factory); this.executors = []; this.erc20Service = erc20Service; this.governanceTokenDelegationService = governanceTokenDelegationService; this.subgraphClient = subgraphClient; const { network } = this.config; const isHardhatNetwork = network === types_2.Network.hardhat; if (isHardhatNetwork && !hardhatGovernanceAddresses) { throw new Error('Must specify governance addresses when on hardhat network'); } const governanceAddresses = isHardhatNetwork ? hardhatGovernanceAddresses : config_1.dydxGovernanceAddresses[network]; const { DYDX_GOVERNANCE, DYDX_GOVERNANCE_EXECUTOR_SHORT, DYDX_GOVERNANCE_EXECUTOR_LONG, DYDX_GOVERNANCE_EXECUTOR_MERKLE_PAUSER, DYDX_GOVERNANCE_PRIORITY_EXECUTOR_STARKWARE, DYDX_GOVERNANCE_STRATEGY, } = governanceAddresses; this.dydxGovernanceAddress = DYDX_GOVERNANCE; this.dydxGovernanceStrategyAddress = DYDX_GOVERNANCE_STRATEGY; this.executors[GovernanceMethodTypes_1.ExecutorType.Short] = DYDX_GOVERNANCE_EXECUTOR_SHORT; this.executors[GovernanceMethodTypes_1.ExecutorType.Long] = DYDX_GOVERNANCE_EXECUTOR_LONG; this.executors[GovernanceMethodTypes_1.ExecutorType.Merkle] = DYDX_GOVERNANCE_EXECUTOR_MERKLE_PAUSER; this.executors[GovernanceMethodTypes_1.ExecutorType.Starkware] = DYDX_GOVERNANCE_PRIORITY_EXECUTOR_STARKWARE; this.governanceTokens = governanceTokens; } async create({ user, targets, values, signatures, calldatas, withDelegateCalls, ipfsHash, executor, }) { const txs = []; const govContract = this.getContractInstance(this.dydxGovernanceAddress); const txCallback = this.generateTxCallback({ rawTxMethod: () => govContract.populateTransaction.create(this.executors[executor], targets, values, signatures, calldatas, withDelegateCalls, ipfsHash), from: user, }); txs.push({ tx: txCallback, txType: types_2.eEthereumTxType.GOVERNANCE_ACTION, gas: this.generateTxPriceEstimation(txs, txCallback), }); return txs; } async cancel({ user, proposalId }) { const txs = []; const govContract = this.getContractInstance(this.dydxGovernanceAddress); const txCallback = this.generateTxCallback({ rawTxMethod: () => govContract.populateTransaction.cancel(proposalId), from: user, }); txs.push({ tx: txCallback, txType: types_2.eEthereumTxType.GOVERNANCE_ACTION, gas: this.generateTxPriceEstimation(txs, txCallback), }); return txs; } async queue({ user, proposalId }) { const txs = []; const govContract = this.getContractInstance(this.dydxGovernanceAddress); const txCallback = this.generateTxCallback({ rawTxMethod: () => govContract.populateTransaction.queue(proposalId), from: user, }); txs.push({ tx: txCallback, txType: types_2.eEthereumTxType.GOVERNANCE_ACTION, gas: this.generateTxPriceEstimation(txs, txCallback), }); return txs; } async execute({ user, proposalId }) { const txs = []; const govContract = this.getContractInstance(this.dydxGovernanceAddress); const txCallback = this.generateTxCallback({ rawTxMethod: () => govContract.populateTransaction.execute(proposalId), from: user, }); txs.push({ tx: txCallback, txType: types_2.eEthereumTxType.GOVERNANCE_ACTION, gas: this.generateTxPriceEstimation(txs, txCallback), }); return txs; } async submitVote({ user, proposalId, support }) { const txs = []; const govContract = this.getContractInstance(this.dydxGovernanceAddress); const txCallback = this.generateTxCallback({ rawTxMethod: () => govContract.populateTransaction.submitVote(proposalId, support), from: user, }); txs.push({ tx: txCallback, txType: types_2.eEthereumTxType.GOVERNANCE_ACTION, gas: this.generateTxPriceEstimation(txs, txCallback), }); return txs; } async signVoting({ support, proposalId }) { const typeData = { types: { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }, ], VoteEmitted: [ { name: 'id', type: 'uint256' }, { name: 'support', type: 'bool' }, ], }, primaryType: 'VoteEmitted', domain: { name: 'Dydx Governance', chainId: types_2.ChainId[this.config.network], verifyingContract: this.dydxGovernanceAddress, }, message: { support, id: proposalId, }, }; return JSON.stringify(typeData); } async submitVoteBySignature({ user, proposalId, support, signature }) { const txs = []; const govContract = this.getContractInstance(this.dydxGovernanceAddress); const sig = ethers_1.utils.splitSignature(signature); const txCallback = this.generateTxCallback({ rawTxMethod: () => govContract.populateTransaction.submitVoteBySignature(proposalId, support, sig.v, sig.r, sig.s), from: user, }); txs.push({ tx: txCallback, txType: types_2.eEthereumTxType.GOVERNANCE_ACTION, gas: this.generateTxPriceEstimation(txs, txCallback), }); return txs; } async getPropositionPowerAt({ user, block, strategy, }) { const { provider } = this.config; const proposalStrategy = types_1.GovernanceStrategy__factory.connect(strategy, provider); const power = await proposalStrategy.getPropositionPowerAt(user, block.toString()); return (0, utils_1.formatEther)(power); } async getVotingPowerAt({ user, block, strategy, }) { const { provider } = this.config; const proposalStrategy = types_1.GovernanceStrategy__factory.connect(strategy, provider); const power = await proposalStrategy.getVotingPowerAt(user, block.toString()); return (0, utils_1.formatEther)(power); } async getTotalPropositionSupplyAt({ block, strategy, }) { const { provider } = this.config; const proposalStrategy = types_1.GovernanceStrategy__factory.connect(strategy, provider); const total = await proposalStrategy.getTotalPropositionSupplyAt(block.toString()); return (0, utils_1.formatEther)(total); } async getTotalVotingSupplyAt({ block, strategy, }) { const { provider } = this.config; const proposalStrategy = types_1.GovernanceStrategy__factory.connect(strategy, provider); const total = await proposalStrategy.getTotalVotingSupplyAt(block.toString()); return (0, utils_1.formatEther)(total); } async getCurrentPropositionPower(user) { const { provider } = this.config; const block = await provider.getBlock('latest'); const latestBlock = block.number; return this.getPropositionPowerAt({ user, block: latestBlock, strategy: this.dydxGovernanceStrategyAddress }); } async getCurrentVotingPower(user) { const { provider } = this.config; const block = await provider.getBlock('latest'); const latestBlock = block.number; return this.getVotingPowerAt({ user, block: latestBlock, strategy: this.dydxGovernanceStrategyAddress }); } async delegatePropositionAndVotingPower(user, delegatee) { const nonzeroTokenBalances = await (0, helpers_1.filterZeroTokenBalances)(user, this.erc20Service, [this.governanceTokens.TOKEN, this.governanceTokens.STAKED_TOKEN]); const txs = (0, lodash_1.flatten)(await Promise.all(nonzeroTokenBalances.map(async (governanceToken) => this.governanceTokenDelegationService.delegate({ user, delegatee, governanceToken, })))); return txs; } async getUserDelegatees(user) { const [parsedTokenBalance, parsedStakedTokenBalance,] = await Promise.all([ this.erc20Service.balanceOf(this.governanceTokens.TOKEN, user), this.erc20Service.balanceOf(this.governanceTokens.STAKED_TOKEN, user) ]); const tokenBalance = (0, parsings_1.parseNumberToEthersBigNumber)(parsedTokenBalance, config_1.DYDX_TOKEN_DECIMALS); const stakedTokenBalance = (0, parsings_1.parseNumberToEthersBigNumber)(parsedStakedTokenBalance, config_1.DYDX_TOKEN_DECIMALS); const userTokenDelegatees = {}; if (!tokenBalance.isZero()) { userTokenDelegatees.TOKEN = await this.governanceTokenDelegationService.getDelegatees(user, this.governanceTokens.TOKEN); } if (!stakedTokenBalance.isZero()) { userTokenDelegatees.STAKED_TOKEN = await this.governanceTokenDelegationService.getDelegatees(user, this.governanceTokens.STAKED_TOKEN); } return userTokenDelegatees; } async delegateVotingPower(user, delegatee) { return this.delegatePower(user, delegatee, types_2.DelegationType.VOTING_POWER); } async delegatePropositionPower(user, delegatee) { return this.delegatePower(user, delegatee, types_2.DelegationType.PROPOSITION_POWER); } async delegatePower(user, delegatee, delegationType) { const nonzeroTokenBalances = await (0, helpers_1.filterZeroTokenBalances)(user, this.erc20Service, [this.governanceTokens.TOKEN, this.governanceTokens.STAKED_TOKEN]); const txs = (0, lodash_1.flatten)(await Promise.all(nonzeroTokenBalances.map(async (governanceToken) => this.governanceTokenDelegationService.delegateByType({ user, delegatee, governanceToken, delegationType, })))); return txs; } async getVoteOnProposal({ proposalId, user, }) { const govContract = this.getContractInstance(this.dydxGovernanceAddress); return govContract.getVoteOnProposal(proposalId, user); } async getLatestProposals(limit) { const governance = this.getContractInstance(this.dydxGovernanceAddress); const [numProposals, votingDelay,] = await Promise.all([ governance.getProposalsCount(), governance.getVotingDelay(), ]); let numProposalsToFetch = numProposals.gt(limit) ? limit : numProposals.toNumber(); const getProposalDataAndStateRequests = []; // proposal IDs are 0-indexed let currentProposalId = numProposals.sub(1).toNumber(); while (numProposalsToFetch > 0) { getProposalDataAndStateRequests.push((0, governance_1.getProposalDataAndStateById)(governance, currentProposalId)); currentProposalId--; numProposalsToFetch--; } const proposalDataAndStates = await Promise.all(getProposalDataAndStateRequests); const { provider } = this.config; return Promise.all(proposalDataAndStates.map(async (pdas) => { const strategy = types_1.GovernanceStrategy__factory.connect(pdas.proposal.strategy, provider); const executor = types_1.Executor__factory.connect(pdas.proposal.executor, provider); const [votingSupply, executorVotingData, ipfsProposalMetadata,] = await Promise.all([ (0, governance_1.getStrategyVotingSupplyForProposal)(strategy, pdas.proposal.startBlock.toNumber()), (0, governance_1.getExecutorVotingData)(executor), (0, ipfs_1.getProposalMetadata)(pdas.proposal.ipfsHash, this.config.ipfsTimeoutMs), ]); const numVotesMinQuorum = votingSupply .mul(executorVotingData.minimumQuorum) .div(executorVotingData.executorVotingPrecision); const numVotesVoteDifferential = votingSupply .mul(executorVotingData.voteDifferential) .div(executorVotingData.executorVotingPrecision); const onchainProposalId = pdas.proposal.id.toNumber(); return { ...ipfsProposalMetadata, dipId: ipfsProposalMetadata.dipId || onchainProposalId, id: onchainProposalId, creator: pdas.proposal.creator, executor: pdas.proposal.executor, strategy: pdas.proposal.strategy, executed: pdas.proposal.executed, canceled: pdas.proposal.canceled, startBlock: pdas.proposal.startBlock.toNumber(), endBlock: pdas.proposal.endBlock.toNumber(), executionTime: pdas.proposal.executionTime.toString(), forVotes: (0, utils_1.formatEther)(pdas.proposal.forVotes), againstVotes: (0, utils_1.formatEther)(pdas.proposal.againstVotes), state: pdas.proposalState, proposalCreated: pdas.proposal.startBlock.sub(votingDelay).toNumber(), totalVotingSupply: (0, utils_1.formatEther)(votingSupply), executionTimeWithGracePeriod: pdas.proposal.executionTime.isZero() ? pdas.proposal.executionTime.toString() : pdas.proposal.executionTime.add(executorVotingData.gracePeriod).toString(), minimumQuorum: (0, utils_1.formatEther)(numVotesMinQuorum), minimumDiff: (0, utils_1.formatEther)(numVotesVoteDifferential), }; })); } async getProposalMetadata(proposalId, limit) { const [topForVotes, topAgainstVotes,] = await Promise.all([ (0, subgraph_1.executeGetSortedProposalVotesQuery)(this.subgraphClient, proposalId, true, limit), (0, subgraph_1.executeGetSortedProposalVotesQuery)(this.subgraphClient, proposalId, false, limit), ]); return { id: proposalId, topForVotes, topAgainstVotes, }; } async getGovernanceVoters(endBlock, startBlock = config_1.DYDX_GOVERNOR_DEPLOYMENT_BLOCK) { const governor = this.getContractInstance(this.dydxGovernanceAddress); const filter = governor.filters.VoteEmitted(null, null, null, null); const events = await governor.queryFilter(filter, startBlock, endBlock); // event VoteEmitted(uint256 id, address indexed voter, bool support, uint256 votingPower); // see event: https://github.com/dydxfoundation/governance-contracts/blob/master/contracts/interfaces/IDydxGovernor.sol#L122 return new Set(events.map((event) => event.args[1])); } } __decorate([ methodValidators_1.GovValidator, __param(0, (0, paramValidators_1.IsEthAddress)('user')) ], DydxGovernanceService.prototype, "create", null); __decorate([ methodValidators_1.GovValidator, __param(0, (0, paramValidators_1.IsEthAddress)('user')), __param(0, (0, paramValidators_1.Is0OrPositiveAmount)('proposalId')) ], DydxGovernanceService.prototype, "cancel", null); __decorate([ methodValidators_1.GovValidator, __param(0, (0, paramValidators_1.IsEthAddress)('user')), __param(0, (0, paramValidators_1.Is0OrPositiveAmount)('proposalId')) ], DydxGovernanceService.prototype, "queue", null); __decorate([ methodValidators_1.GovValidator, __param(0, (0, paramValidators_1.IsEthAddress)('user')), __param(0, (0, paramValidators_1.Is0OrPositiveAmount)('proposalId')) ], DydxGovernanceService.prototype, "execute", null); __decorate([ methodValidators_1.GovValidator, __param(0, (0, paramValidators_1.IsEthAddress)('user')), __param(0, (0, paramValidators_1.Is0OrPositiveAmount)('proposalId')) ], DydxGovernanceService.prototype, "submitVote", null); __decorate([ methodValidators_1.GovValidator, __param(0, (0, paramValidators_1.Is0OrPositiveAmount)('proposalId')) ], DydxGovernanceService.prototype, "signVoting", null); __decorate([ methodValidators_1.GovValidator, __param(0, (0, paramValidators_1.IsEthAddress)('user')), __param(0, (0, paramValidators_1.Is0OrPositiveAmount)('proposalId')) ], DydxGovernanceService.prototype, "submitVoteBySignature", null); __decorate([ methodValidators_1.GovValidator ], DydxGovernanceService.prototype, "getPropositionPowerAt", null); __decorate([ methodValidators_1.GovValidator ], DydxGovernanceService.prototype, "getVotingPowerAt", null); __decorate([ methodValidators_1.GovValidator ], DydxGovernanceService.prototype, "getTotalPropositionSupplyAt", null); __decorate([ methodValidators_1.GovValidator ], DydxGovernanceService.prototype, "getTotalVotingSupplyAt", null); __decorate([ methodValidators_1.GovValidator ], DydxGovernanceService.prototype, "getVoteOnProposal", null); exports.default = DydxGovernanceService;