@dydxfoundation/governance
Version:
dYdX governance smart contracts
370 lines (369 loc) • 19 kB
JavaScript
"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;