Skip to content

Commit

Permalink
Delegate accountability
Browse files Browse the repository at this point in the history
  • Loading branch information
mcgingras authored Nov 8, 2024
1 parent 56be3ae commit 0e55fd4
Show file tree
Hide file tree
Showing 52 changed files with 1,502 additions and 347 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ next-env.d.ts

dist

# version manager
.tool-versions
11 changes: 6 additions & 5 deletions src/app/api/common/citizens/getCitizens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ async function getCitizens({
sort = "shuffle",
seed,
}: {
pagination: PaginationParams;
pagination?: PaginationParams;
sort: string;
seed?: number;
}): Promise<PaginatedResult<DelegateChunk[]>> {
Expand All @@ -26,7 +26,7 @@ async function getCitizens({
if (sort === "shuffle") {
return prisma.$queryRawUnsafe<DelegatesGetPayload[]>(
`
SELECT
SELECT
citizens.address AS delegate,
delegate.voting_power,
advanced_vp,
Expand Down Expand Up @@ -54,7 +54,7 @@ async function getCitizens({
WHERE citizens.retro_funding_round = (SELECT MAX(retro_funding_round) FROM agora.citizens)
ORDER BY md5(CAST(citizens.address AS TEXT) || CAST($2 AS TEXT))
OFFSET $3
LIMIT $4;
LIMIT $4;
`,
slug,
seed,
Expand All @@ -64,7 +64,7 @@ async function getCitizens({
} else {
return prisma.$queryRawUnsafe<DelegatesGetPayload[]>(
`
SELECT
SELECT
citizens.address AS delegate,
delegate.voting_power,
direct_vp,
Expand Down Expand Up @@ -92,7 +92,7 @@ async function getCitizens({
AND citizens.dao_slug = $1::config.dao_slug
WHERE citizens.retro_funding_round = (SELECT MAX(retro_funding_round) FROM agora.citizens)
ORDER BY COALESCE(delegate.voting_power, 0) DESC,
citizens.address ASC
citizens.address ASC
OFFSET $2
LIMIT $3;
`,
Expand All @@ -114,6 +114,7 @@ async function getCitizens({
direct: citizen.direct_vp?.toFixed(0) || "0",
advanced: citizen.advanced_vp?.toFixed(0) || "0",
},
votingParticipation: 0,
citizen: citizen.citizen,
statement: citizen.statement,
})),
Expand Down
93 changes: 76 additions & 17 deletions src/app/api/common/delegates/getDelegates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { fetchCurrentQuorum } from "@/app/api/common/quorum/getQuorum";
import { fetchVotableSupply } from "@/app/api/common/votableSupply/getVotableSupply";
import { doInSpan } from "@/app/lib/logging";
import { TENANT_NAMESPACES } from "@/lib/constants";
import { getProxyAddress } from "@/lib/alligatorUtils";

/*
* Fetches a list of delegates
Expand All @@ -35,10 +36,11 @@ async function getDelegates({
seed,
filters,
}: {
pagination: PaginationParams;
pagination?: PaginationParams;
sort: string;
seed?: number;
filters?: {
delegator?: `0x${string}`;
issues?: string;
stakeholders?: string;
endorsed?: boolean;
Expand Down Expand Up @@ -102,22 +104,57 @@ async function getDelegates({
: "";

let delegateUniverseCTE: string;

const tokenAddress = contracts.token.address;
const proxyAddress = filters?.delegator
? await getProxyAddress(filters?.delegator?.toLowerCase())
: null;

delegateUniverseCTE = `with del_statements as (select address from agora.delegate_statements where dao_slug='${slug}'),
del_with_del as (select * from ${namespace + ".delegates"} d where contract = '${tokenAddress}'),
del_card_universe as (select COALESCE(d.delegate, ds.address) as delegate,
coalesce(d.num_of_delegators, 0) as num_of_delegators,
coalesce(d.direct_vp, 0) as direct_vp,
coalesce(d.advanced_vp, 0) as advanced_vp,
coalesce(d.voting_power, 0) as voting_power
from del_with_del d full join del_statements ds on d.delegate = ds.address)`;
delegateUniverseCTE = `
with del_statements as (
select address
from agora.delegate_statements
where dao_slug='${slug}'
),
filtered_delegates as (
select d.*
from ${namespace}.delegates d
where d.contract = '${tokenAddress}'
${
filters?.delegator
? `
AND d.delegate IN (
SELECT delegatee
FROM (
SELECT delegatee, block_number
FROM ${namespace}.delegatees
WHERE delegator = '${filters.delegator.toLowerCase()}'
${proxyAddress ? `AND delegatee <> '${proxyAddress.toLowerCase()}'` : ""}
AND contract = '${tokenAddress}'
UNION ALL
SELECT "to" as delegatee, block_number
FROM ${namespace}.advanced_delegatees
WHERE "from" = '${filters.delegator.toLowerCase()}'
AND delegated_amount > 0
AND contract = '${contracts.alligator?.address || tokenAddress}'
) combined_delegations
ORDER BY block_number DESC
)
`
: ""
}
),
del_card_universe as (
select
d.delegate as delegate,
d.num_of_delegators as num_of_delegators,
d.direct_vp as direct_vp,
d.advanced_vp as advanced_vp,
d.voting_power as voting_power
from filtered_delegates d
)`;

// Applies allow-list filtering to the delegate list
const paginatedAllowlistQuery = async (skip: number, take: number) => {
console.log(sort);

const allowListString = allowList.map((value) => `'${value}'`).join(", ");

switch (sort) {
Expand Down Expand Up @@ -154,13 +191,12 @@ async function getDelegates({
) AS statement
FROM del_card_universe d
WHERE num_of_delegators IS NOT NULL
AND (ARRAY_LENGTH(ARRAY[${allowListString}]::text[], 1) IS NULL OR delegate = ANY(ARRAY[${allowListString}]::text[]))
AND (ARRAY_LENGTH(ARRAY[${allowListString}]::text[], 1) IS NULL OR d.delegate = ANY(ARRAY[${allowListString}]::text[]))
${delegateStatementFiler}
ORDER BY num_of_delegators DESC
OFFSET $1
LIMIT $2;
`;
// console.log(QRY1);
`;
return prisma.$queryRawUnsafe<DelegatesGetPayload[]>(QRY1, skip, take);

case "weighted_random":
Expand Down Expand Up @@ -202,7 +238,6 @@ async function getDelegates({
OFFSET $1
LIMIT $2;
`;
// console.log(QRY2);
return prisma.$queryRawUnsafe<DelegatesGetPayload[]>(QRY2, skip, take);

default:
Expand Down Expand Up @@ -243,7 +278,6 @@ async function getDelegates({
OFFSET $1
LIMIT $2;
`;
// console.log(QRY3);
return prisma.$queryRawUnsafe<DelegatesGetPayload[]>(QRY3, skip, take);
}
};
Expand Down Expand Up @@ -436,5 +470,30 @@ async function getDelegate(addressOrENSName: string): Promise<Delegate> {
};
}

async function getVoterStats(addressOrENSName: string): Promise<any> {
const { namespace, contracts } = Tenant.current();
const address = isAddress(addressOrENSName)
? addressOrENSName.toLowerCase()
: await resolveENSName(addressOrENSName);

const statsQuery = await prisma.$queryRawUnsafe<
Pick<DelegateStats, "voter" | "participation_rate" | "last_10_props">[]
>(
`
SELECT
voter,
participation_rate,
last_10_props
FROM ${namespace + ".voter_stats"}
WHERE voter = $1 AND contract = $2
`,
address,
contracts.governor.address
);

return statsQuery?.[0] || undefined;
}

export const fetchDelegates = cache(getDelegates);
export const fetchDelegate = cache(getDelegate);
export const fetchVoterStats = cache(getVoterStats);
33 changes: 18 additions & 15 deletions src/app/api/common/delegations/getDelegations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,51 +151,51 @@ async function getCurrentDelegatorsForAddress({

// Replace with the Agora Governor flag
if (contracts.alligator || namespace === TENANT_NAMESPACES.SCROLL) {
advancedDelegatorsSubQry = `SELECT
advancedDelegatorsSubQry = `SELECT
"from",
"to",
delegated_amount as allowance,
'ADVANCED' AS type,
'ADVANCED' AS type,
block_number,
CASE WHEN delegated_share >= 1 THEN 'FULL' ELSE 'PARTIAL' END as amount,
transaction_hash
FROM
FROM
${namespace}.advanced_delegatees ad
WHERE
WHERE
ad."to" = $1
AND delegated_amount > 0
AND delegated_amount > 0
AND contract = $2`;
} else {
advancedDelegatorsSubQry = `WITH ghost as (SELECT
advancedDelegatorsSubQry = `WITH ghost as (SELECT
null::text as "from",
null::text as "to",
null::numeric as allowance,
'ADVANCED' AS type,
'ADVANCED' AS type,
null::numeric as block_number,
'FULL' as amount,
null::text as transaction_hash)
select * from ghost
WHERE
WHERE
ghost."to" = $1
AND ghost."from" = $2`;
}

if (namespace == TENANT_NAMESPACES.SCROLL) {
directDelegatorsSubQry = `WITH ghost as (SELECT
directDelegatorsSubQry = `WITH ghost as (SELECT
null::text as "from",
null::text as "to",
null::numeric as allowance,
'DIRECT' AS type,
'DIRECT' AS type,
null::numeric as block_number,
'FULL' as amount,
null::text as transaction_hash)
select * from ghost
WHERE
WHERE
ghost."to" = $3
AND ghost."from" = $3`;
} else {
directDelegatorsSubQry = `
SELECT
SELECT
"from",
"to",
null::numeric as allowance,
Expand Down Expand Up @@ -251,7 +251,7 @@ async function getCurrentDelegatorsForAddress({
>(
`
WITH advanced_delegatees AS ( ${advancedDelegatorsSubQry} )
, direct_delegatees AS ( ${directDelegatorsSubQry} )
SELECT * FROM advanced_delegatees
UNION ALL
Expand Down Expand Up @@ -357,10 +357,13 @@ const getDirectDelegateeForAddress = async ({
}: {
address: string;
}) => {
const { namespace } = Tenant.current();
const { namespace, contracts } = Tenant.current();

const delegatee = await prisma[`${namespace}Delegatees`].findFirst({
where: { delegator: address.toLowerCase() },
where: {
delegator: address.toLowerCase(),
contract: contracts.token.address.toLowerCase(),
},
});

if (namespace === TENANT_NAMESPACES.OPTIMISM) {
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/common/proposals/proposal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export type Proposal = {
queuedTime: Date | null;
markdowntitle: string;
description: string | null;
quorum: BigNumberish | null;
approvalThreshold: BigNumberish | null;
quorum: bigint | null;
approvalThreshold: bigint | null;
proposalData: ParsedProposalData[ProposalType]["kind"];
unformattedProposalData: `0x${string}` | null | any;
proposalResults: ParsedProposalResults[ProposalType]["kind"];
Expand Down
65 changes: 63 additions & 2 deletions src/app/api/common/votes/getVotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,64 @@ async function getSnapshotVotesForDelegateForAddress({
}
}

async function getVotersWhoHaveNotVotedForProposal({
proposalId,
pagination = { offset: 0, limit: 20 },
}: {
proposalId: string;
pagination?: PaginationParams;
}) {
const { namespace, contracts, slug } = Tenant.current();

const queryFunction = (skip: number, take: number) => {
const notVotedQuery = `
SELECT
del.*,
ds.twitter,
ds.discord,
ds.warpcast
FROM ${namespace + ".delegates"} del
LEFT JOIN agora.delegate_statements ds
ON del.delegate = ds.address
AND ds.dao_slug = '${slug}'
WHERE del.delegate NOT IN (
SELECT voter FROM ${namespace + ".vote_cast_events"} WHERE proposal_id = $1
)
AND del.contract = $2
ORDER BY del.voting_power DESC
`;

return prisma.$queryRawUnsafe<VotePayload[]>(
`${notVotedQuery}
OFFSET $3
LIMIT $4;`,
proposalId,
contracts.token.address.toLowerCase(),
skip,
take
);
};

const [{ meta, data: nonVoters }, latestBlock] = await Promise.all([
doInSpan({ name: "getVotersWhoHaveNotVotedForProposal" }, async () =>
paginateResult(queryFunction, pagination)
),
contracts.token.provider.getBlock("latest"),
]);

if (!nonVoters || nonVoters.length === 0) {
return {
meta,
data: [],
};
}

return {
meta,
data: nonVoters,
};
}

async function getVotesForProposal({
proposalId,
pagination = { offset: 0, limit: 20 },
Expand Down Expand Up @@ -255,8 +313,8 @@ async function getVotesForProposal({
) q
ORDER BY ${sort} DESC
OFFSET $3
LIMIT $4;
`;
LIMIT $4;`;

return prisma.$queryRawUnsafe<VotePayload[]>(
query,
proposalId,
Expand Down Expand Up @@ -372,3 +430,6 @@ export const fetchUserVotesForProposal = cache(getUserVotesForProposal);
export const fetchVotesForProposalAndDelegate = cache(
getVotesForProposalAndDelegate
);
export const fetchVotersWhoHaveNotVotedForProposal = cache(
getVotersWhoHaveNotVotedForProposal
);
Loading

0 comments on commit 0e55fd4

Please sign in to comment.