Skip to content

Commit

Permalink
feat: add optigov delegates task
Browse files Browse the repository at this point in the history
  • Loading branch information
luisvid committed Oct 28, 2024
1 parent 9818ac0 commit 1b426fa
Show file tree
Hide file tree
Showing 4 changed files with 454 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/ethernaut-optigov/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ This plugin doesn't depend on any other plugins.
This plugin adds the tasks listed below.

- login Logs in to the Agora RetroPGF API with SIWE (Sign in with Ethereum)
- projects Prints a list of projects registered in RetroPGF, given specified filters
- proposals Prints a list of proposals registered in RetroPGF, given specified filters
- delegate Prints a list of delegates on Agora, or details a specific delegate, with optional related data (votes or delegators)


## Environment extensions

Expand Down
132 changes: 132 additions & 0 deletions packages/ethernaut-optigov/src/internal/agora/Delegates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
const debug = require('ethernaut-common/src/ui/debug')

class Delegates {
constructor(agora) {
this.agora = agora
}

// Get a list of delegates with pagination
async getDelegates({ limit = 10, offset = 0, sort } = {}) {
try {
const axiosInstance = this.agora.createAxiosInstance()
const response = await axiosInstance.get('/delegates', {
params: { limit, offset, sort },
})

debug.log(`Delegates: ${response.data.data}`, 'ethernaut-optigov')
return response.data.data.map((delegate) => ({
address: delegate.address,
votingPower: delegate.votingPower?.total,
twitter: delegate.statement?.payload?.twitter,
discord: delegate.statement?.payload?.discord,
delegateStatement:
delegate.statement?.payload?.delegateStatement?.substring(0, 100),
}))
} catch (error) {
this.agora.handleError(error)
}
}

// Get a specific delegate by address or ENS name
async getDelegateById(addressOrEnsName) {
try {
const axiosInstance = this.agora.createAxiosInstance()
const response = await axiosInstance.get(`/delegates/${addressOrEnsName}`)

debug.log(`Delegate: ${response.data}`, 'ethernaut-optigov')
const data = response.data
return {
address: data.address,
votingPower: {
advanced: data.votingPower.advanced,
direct: data.votingPower.direct,
total: data.votingPower.total,
},
votingPowerRelativeToVotableSupply:
data.votingPowerRelativeToVotableSupply,
votingPowerRelativeToQuorum: data.votingPowerRelativeToQuorum,
proposalsCreated: data.proposalsCreated,
proposalsVotedOn: data.proposalsVotedOn,
votedFor: data.votedFor,
votedAgainst: data.votedAgainst,
votedAbstain: data.votedAbstain,
votingParticipation: data.votingParticipation,
lastTenProps: data.lastTenProps,
numOfDelegators: data.numOfDelegators,
}
} catch (error) {
this.agora.handleError(error)
}
}

// Get a paginated list of votes for a specific delegate
async getDelegateVotes({
addressOrEnsName,
limit = 10,
offset = 0,
sort,
} = {}) {
try {
const axiosInstance = this.agora.createAxiosInstance()
const response = await axiosInstance.get(
`/delegates/${addressOrEnsName}/votes`,
{
params: { limit, offset, sort },
},
)

debug.log(
`Votes for Delegate ${addressOrEnsName}: ${response.data.data}`,
'ethernaut-optigov',
)
return response.data.data.map((vote) => ({
transactionHash: vote.transactionHash,
proposalId: vote.proposalId,
address: vote.address,
support: vote.support,
reason: vote.reason,
weight: vote.weight,
proposalValue: vote.proposalValue,
proposalTitle: vote.proposalTitle,
proposalType: vote.proposalType,
timestamp: vote.timestamp,
}))
} catch (error) {
this.agora.handleError(error)
}
}

// Get a paginated list of delegators for a specific delegate
async getDelegateDelegators({
addressOrEnsName,
limit = 10,
offset = 0,
sort,
} = {}) {
try {
const axiosInstance = this.agora.createAxiosInstance()
const response = await axiosInstance.get(
`/delegates/${addressOrEnsName}/delegators`,
{
params: { limit, offset, sort },
},
)

debug.log(
`Delegators for Delegate ${addressOrEnsName}: ${response.data.data}`,
'ethernaut-optigov',
)
return response.data.data.map((delegator) => ({
from: delegator.from,
allowance: delegator.allowance,
timestamp: delegator.timestamp,
type: delegator.type,
amount: delegator.amount,
}))
} catch (error) {
this.agora.handleError(error)
}
}
}

module.exports = Delegates
143 changes: 143 additions & 0 deletions packages/ethernaut-optigov/src/tasks/Delegates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
const types = require('ethernaut-common/src/validation/types')
const output = require('ethernaut-common/src/ui/output')
const Delegates = require('../internal/agora/Delegates')
const Agora = require('../internal/agora/Agora')

const RELATED_DATA = {
votes: 'votes',
delegators: 'delegators',
none: 'none',
}

require('../scopes/optigov')
.task(
'delegates',
'Prints a list of delegates on Agora, or a specific delegate, with optional votes or delegator related data',
)
.addOptionalParam(
'limit',
'The maximum number of delegates to fetch. Defaults to 10.',
10,
types.int,
)
.addOptionalParam(
'offset',
'The number of delegates to skip before starting to fetch. Defaults to 0.',
0,
types.int,
)
.addOptionalParam(
'address',
'The address or ENS name of a specific delegate to query.',
undefined,
types.string,
)
.addOptionalParam(
'relatedData',
'If specified, fetch additional related data such as votes or delegators for the given address or ENS name.',
RELATED_DATA.none,
types.string,
)
.setAction(async ({ limit, offset, address, relatedData }) => {
try {
const agora = new Agora()
const delegates = new Delegates(agora)

if (address) {
if (relatedData === RELATED_DATA.votes) {
// Get votes
const delegateVotes = await delegates.getDelegateVotes({
addressOrEnsName: address,
limit,
offset,
})
return output.resultBox(
printVotes(delegateVotes),
`Votes for Delegate ${address}`,
)
} else if (relatedData === RELATED_DATA.delegators) {
// Get delegators
const delegateDelegators = await delegates.getDelegateDelegators({
addressOrEnsName: address,
limit,
offset,
})
return output.resultBox(
printDelegators(delegateDelegators),
`Delegators for Delegate ${address}`,
)
} else {
// Get the specific delegate
const delegate = await delegates.getDelegateById(address)
return output.resultBox(
printDelegate(delegate),
`Delegate ${address}`,
)
}
}

// If no specific address or ENS is given, fetch the list of delegates
const delegateList = await delegates.getDelegates({ limit, offset })

return output.resultBox(printDelegates(delegateList), 'Delegates')
} catch (err) {
return output.errorBox(err)
}
})

function printDelegates(delegates) {
const strs = []

for (const delegate of delegates) {
strs.push(
`Address: ${delegate.address}
Voting Power: ${delegate.votingPower}
Twitter: ${delegate.twitter}
Discord: ${delegate.discord}
Statement: ${delegate.delegateStatement}`,
)
}

return strs.join('\n\n')
}

function printDelegate(delegate) {
return `Address: ${delegate.address}
Voting Power (Advanced): ${delegate.votingPower.advanced}
Voting Power (Direct): ${delegate.votingPower.direct}
Voting Power (Total): ${delegate.votingPower.total}
Voting Power Relative to Votable Supply: ${delegate.votingPowerRelativeToVotableSupply}
Voting Power Relative to Quorum: ${delegate.votingPowerRelativeToQuorum}
Proposals Created: ${delegate.proposalsCreated}
Proposals Voted On: ${delegate.proposalsVotedOn}
Voted For: ${delegate.votedFor}
Voted Against: ${delegate.votedAgainst}
Voted Abstain: ${delegate.votedAbstain}
Voting Participation: ${delegate.votingParticipation}
Last Ten Proposals: ${delegate.lastTenProps}
Number of Delegators: ${delegate.numOfDelegators}`
}

function printVotes(votes) {
const strs = []

for (const vote of votes) {
strs.push(
` - Support: ${vote.support}, Weight: ${vote.weight}, Proposal: ${vote.proposalTitle} (ID: ${vote.proposalId}), Timestamp: ${vote.timestamp}`,
)
}

return strs.join('\n\n')
}

function printDelegators(delegators) {
const strs = []

for (const delegator of delegators) {
strs.push(
` - From: ${delegator.from}, Allowance: ${delegator.allowance}, Type: ${delegator.type}, Amount: ${delegator.amount}, Timestamp: ${delegator.timestamp}`,
)
}

return strs.join('\n\n')
}
Loading

0 comments on commit 1b426fa

Please sign in to comment.