@swaptoshi/governance-module
Version:
Klayr governance on-chain module
208 lines • 10.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProposalQueue = void 0;
const codec_1 = require("@klayr/codec");
const cryptography = require("@klayr/cryptography");
const utils = require("@klayr/utils");
const types_1 = require("../../types");
const queue_1 = require("../queue");
const base_1 = require("./base");
const utils_1 = require("../../utils");
const utils_2 = require("@swaptoshi/utils");
const utils_3 = require("@swaptoshi/utils");
const proposal_1 = require("../proposal");
const proposal_active_1 = require("../../events/proposal_active");
const proposal_quorum_checked_1 = require("../../events/proposal_quorum_checked");
const proposal_outcome_1 = require("../../events/proposal_outcome");
const schema_1 = require("../../schema");
const proposal_executed_1 = require("../../events/proposal_executed");
const proposal_voter_1 = require("../proposal_voter");
const casted_vote_1 = require("../casted_vote");
const payload_1 = require("../../utils/payload");
class ProposalQueue extends base_1.BaseInstance {
constructor(stores, events, config, genesisConfig, moduleName, governableConfigRegistry, queue, height) {
super(queue_1.ProposalQueueStore, stores, events, config, genesisConfig, moduleName, utils_2.bytes.numberToBytes(height));
this.start = [];
this.quorum = [];
this.ends = [];
this.execute = [];
Object.assign(this, utils.objects.cloneDeep(queue));
this.proposalStore = this.stores.get(proposal_1.ProposalStore);
this.proposalVoterStore = this.stores.get(proposal_voter_1.ProposalVoterStore);
this.castedVoteStore = this.stores.get(casted_vote_1.CastedVoteStore);
this.governableConfigRegistry = governableConfigRegistry;
}
toJSON() {
return utils.objects.cloneDeep(utils_3.object.serializer({
start: this.start,
quorum: this.quorum,
ends: this.ends,
execute: this.execute,
}));
}
toObject() {
return utils.objects.cloneDeep({
start: this.start,
quorum: this.quorum,
ends: this.ends,
execute: this.execute,
});
}
async executeQueue() {
this._checkMutableDependencies();
for (const startedProposal of this.start) {
await this._executeStartedProposal(startedProposal);
}
for (const proposalQuorumCheck of this.quorum) {
await this._executeQuorumCheck(proposalQuorumCheck);
}
for (const endedProposal of this.ends) {
await this._executeEndedProposal(endedProposal);
}
for (const executedProposal of this.execute) {
await this._executeProposalOutcome(executedProposal);
}
}
async _executeStartedProposal(proposalId) {
this._checkMutableDependencies();
const proposal = await this.proposalStore.getMutableProposal(this.mutableContext, proposalId);
await proposal.setStatus(types_1.ProposalStatus.ACTIVE);
const events = this.events.get(proposal_active_1.ProposalActiveEvent);
events.add(this.mutableContext.context, {
proposalId,
status: types_1.ProposalStatus.ACTIVE,
}, [proposal.author]);
}
async _executeQuorumCheck(proposalId) {
this._checkMutableDependencies();
const proposal = await this.proposalStore.getMutableProposal(this.mutableContext, proposalId);
if (proposal.status !== types_1.ProposalStatus.ACTIVE)
return;
const stakingTokenId = this._getStakingTokenId();
const config = await this.config.getConfig(this.mutableContext.context);
const totalSupplyStore = await this.tokenMethod.getTotalSupply(this.mutableContext.context);
const index = totalSupplyStore.totalSupply.findIndex(supply => supply.tokenID.equals(stakingTokenId));
const treshold = (0, utils_1.parseBigintOrPercentage)(proposal.parameters.quorumTreshold, totalSupplyStore.totalSupply[index].totalSupply);
let participant = BigInt(0);
let status = types_1.ProposalStatus.ACTIVE;
switch (proposal.parameters.quorumMode) {
case types_1.QuorumMode.FOR_AGAINST_ABSTAIN:
participant = proposal.turnout.for + proposal.turnout.against + proposal.turnout.abstain;
break;
case types_1.QuorumMode.FOR_AGAINST:
participant = proposal.turnout.for + proposal.turnout.against;
break;
case types_1.QuorumMode.FOR:
participant = proposal.turnout.for;
break;
default:
throw new Error('unknown proposal.parameters.quorumMode');
}
if (participant < treshold) {
status = types_1.ProposalStatus.FAILED_QUORUM;
await this.tokenMethod.unlock(this.mutableContext.context, proposal.author, this.moduleName, stakingTokenId, proposal.deposited);
await this._removeProposalVoterCastedVote(proposalId);
if (config.depositPoolAddress) {
await this.tokenMethod.transfer(this.mutableContext.context, proposal.author, cryptography.address.getAddressFromKlayr32Address(config.depositPoolAddress), stakingTokenId, proposal.deposited);
}
else {
await this.tokenMethod.burn(this.mutableContext.context, proposal.author, stakingTokenId, proposal.deposited);
}
}
await proposal.setStatus(status);
const events = this.events.get(proposal_quorum_checked_1.ProposalQuorumCheckedEvent);
events.add(this.mutableContext.context, {
proposalId,
status,
}, [proposal.author]);
}
async _executeEndedProposal(proposalId) {
this._checkMutableDependencies();
const proposal = await this.proposalStore.getMutableProposal(this.mutableContext, proposalId);
if (proposal.status !== types_1.ProposalStatus.ACTIVE)
return;
const stakingTokenId = this._getStakingTokenId();
await this.tokenMethod.unlock(this.mutableContext.context, proposal.author, this.moduleName, stakingTokenId, proposal.deposited);
let status = types_1.ProposalStatus.ACTIVE;
const totalSupplyStore = await this.tokenMethod.getTotalSupply(this.mutableContext.context);
const index = totalSupplyStore.totalSupply.findIndex(supply => supply.tokenID.equals(stakingTokenId));
const { totalSupply } = totalSupplyStore.totalSupply[index];
if (proposal.voteSummary.for > proposal.voteSummary.against) {
status = types_1.ProposalStatus.ACCEPTED;
}
if (proposal.parameters.enableTurnoutBias) {
const participant = proposal.turnout.for + proposal.turnout.against + proposal.turnout.abstain;
if (!(0, utils_1.isSatisfyTurnoutBias)(proposal.turnout.for, proposal.turnout.against, participant, totalSupply)) {
status = types_1.ProposalStatus.REJECTED;
}
}
await proposal.setStatus(status);
await this._removeProposalVoterCastedVote(proposalId);
const events = this.events.get(proposal_outcome_1.ProposalOutcomeEvent);
events.add(this.mutableContext.context, {
proposalId,
status,
turnoutBiasEnabled: proposal.parameters.enableTurnoutBias,
boostingEnabled: proposal.parameters.enableBoosting,
}, [proposal.author]);
}
async _executeProposalOutcome(proposalId) {
this._checkMutableDependencies();
const proposal = await this.proposalStore.getMutableProposal(this.mutableContext, proposalId);
if (proposal.status !== types_1.ProposalStatus.ACCEPTED)
return;
const events = this.events.get(proposal_executed_1.ProposalExecutedEvent);
const actions = [...proposal.actions].sort((a, b) => {
if (a.type === 'funding' && b.type !== 'funding')
return -1;
if (a.type !== 'funding' && b.type === 'funding')
return 1;
return 0;
});
try {
for (const action of actions) {
if (action.type === 'funding')
await this._executeFundingAction(action.payload);
if (action.type === 'config')
await this._executeConfigAction(action.payload);
}
await proposal.setStatus(types_1.ProposalStatus.EXECUTED);
events.add(this.mutableContext.context, {
proposalId,
status: types_1.ProposalStatus.EXECUTED,
}, [proposal.author]);
}
catch {
await proposal.setStatus(types_1.ProposalStatus.EXECUTED_WITH_ERROR);
events.add(this.mutableContext.context, {
proposalId,
status: types_1.ProposalStatus.EXECUTED_WITH_ERROR,
}, [proposal.author]);
}
}
async _executeFundingAction(encodedPayload) {
this._checkMutableDependencies();
const config = await this.config.getConfig(this.mutableContext.context);
const payload = codec_1.codec.decode(schema_1.fundingActionPayloadSchema, encodedPayload);
await this.tokenMethod.transfer(this.mutableContext.context, cryptography.address.getAddressFromKlayr32Address(config.treasuryAddress), payload.receivingAddress, payload.tokenId, payload.fundingAmount);
}
async _executeConfigAction(encodedPayload) {
this._checkMutableDependencies();
const payload = codec_1.codec.decode(schema_1.configActionPayloadSchema, encodedPayload);
const targetConfig = this.governableConfigRegistry.get(payload.moduleName);
const decodedValue = (0, payload_1.decodeConfigProposalValue)(targetConfig.schema, payload);
await targetConfig.setConfigWithPath(this.mutableContext.context, payload.paramPath, decodedValue);
}
async _removeProposalVoterCastedVote(proposalId) {
this._checkMutableDependencies();
const proposalVoters = await this.proposalVoterStore.getOrDefault(this.mutableContext.context, proposalId);
for (const voter of proposalVoters.voters) {
await this.castedVoteStore.removeCastedVoteByProposalId(this.mutableContext.context, voter, proposalId);
}
}
_getStakingTokenId() {
return Buffer.concat([Buffer.from(this.genesisConfig.chainID, 'hex'), Buffer.from('00000000', 'hex')]);
}
}
exports.ProposalQueue = ProposalQueue;
//# sourceMappingURL=queue.js.map