Skip to content

Commit c24752d

Browse files
authored
Merge pull request #58 from ethernautdao/feature/optigov
Feature/optigov
2 parents 6afe361 + d0e45e3 commit c24752d

File tree

13 files changed

+560
-13
lines changed

13 files changed

+560
-13
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const debug = require('ethernaut-common/src/ui/debug')
2+
3+
class Ballots {
4+
constructor(agora) {
5+
this.agora = agora
6+
}
7+
8+
// Retrieve a specific ballot for a RetroFunding round
9+
async getBallot(roundId, addressOrEnsName) {
10+
try {
11+
const axiosInstance = this.agora.createAxiosInstance()
12+
const response = await axiosInstance.get(
13+
`/retrofunding/rounds/${roundId}/ballots/${addressOrEnsName}`,
14+
)
15+
// Convert response.data to a string if it's not one
16+
const dataString =
17+
typeof response.data === 'string'
18+
? response.data
19+
: JSON.stringify(response.data)
20+
debug.log(`Ballot: ${dataString}`, 'ethernaut-optigov')
21+
return response.data
22+
} catch (error) {
23+
this.agora.handleError(error)
24+
}
25+
}
26+
27+
// Submit the ballot content as final for the round
28+
async submitBallot(roundId, addressOrEnsName, payload) {
29+
try {
30+
const axiosInstance = this.agora.createAxiosInstance()
31+
const response = await axiosInstance.post(
32+
`/retrofunding/rounds/${roundId}/ballots/${addressOrEnsName}/submit`,
33+
payload,
34+
)
35+
debug.log(`Submit Ballot Response: ${response.data}`, 'ethernaut-optigov')
36+
return response.data
37+
} catch (error) {
38+
this.agora.handleError(error)
39+
}
40+
}
41+
}
42+
43+
module.exports = Ballots
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const Agora = require('./Agora')
2+
module.exports = new Agora()
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
const types = require('ethernaut-common/src/validation/types')
2+
const output = require('ethernaut-common/src/ui/output')
3+
const Ballots = require('../internal/agora/Ballots')
4+
const agora = require('../internal/agora/agoraInstance')
5+
6+
require('../scopes/optigov')
7+
.task('ballots', 'Interacts with ballots on Agora: retrieve or submit')
8+
.addParam(
9+
'action',
10+
'Action to perform: "get" or "submit"',
11+
undefined,
12+
types.string,
13+
)
14+
.addParam('roundId', 'RetroFunding round ID', undefined, types.string)
15+
.addParam(
16+
'ballotContent',
17+
'Ballot content as a JSON string (optional for submit)',
18+
undefined,
19+
types.string,
20+
)
21+
22+
.setAction(async ({ action, roundId, ballotContent }, hre) => {
23+
try {
24+
const ballots = new Ballots(agora)
25+
26+
if (action === 'get') {
27+
const signers = await hre.ethers.getSigners()
28+
if (!signers.length) {
29+
throw new Error('No signers available.')
30+
}
31+
const signer = signers[0]
32+
const ballot = await ballots.getBallot(roundId, signer.address)
33+
// Format the ballot output if it's an object
34+
const ballotResult =
35+
typeof ballot === 'string' ? ballot : formatBallot(ballot)
36+
return output.resultBox(
37+
ballotResult,
38+
`Ballot for Round ${roundId} & ${signer.address}`,
39+
)
40+
} else if (action === 'submit') {
41+
const signers = await hre.ethers.getSigners()
42+
if (!signers.length) {
43+
throw new Error('No signers available.')
44+
}
45+
const signer = signers[0]
46+
// Prepare content to be signed
47+
const defaultContent = {
48+
allocations: [{}],
49+
os_only: true,
50+
os_multiplier: 0,
51+
}
52+
const contentToProcess = ballotContent
53+
? ballotContent
54+
: JSON.stringify(defaultContent)
55+
const parsedContent = ballotContent
56+
? JSON.parse(ballotContent)
57+
: defaultContent
58+
const computedSignature = await signer.signMessage(contentToProcess)
59+
60+
const payload = {
61+
address: signer.address,
62+
ballot_content: parsedContent,
63+
signature: computedSignature,
64+
}
65+
const result = await ballots.submitBallot(
66+
roundId,
67+
signer.address,
68+
payload,
69+
)
70+
const resultString =
71+
typeof result === 'string' ? result : JSON.stringify(result)
72+
return output.resultBox(
73+
resultString,
74+
`Submit Ballot for Round ${roundId} & ${signer.address}`,
75+
)
76+
} else {
77+
throw new Error('Invalid action. Use "get" or "submit".')
78+
}
79+
} catch (err) {
80+
return output.errorBox(err)
81+
}
82+
})
83+
84+
// Utility: formats the ballot object for display
85+
function formatBallot(ballot) {
86+
let formatted = ''
87+
formatted += `Address: ${ballot.address}\n`
88+
formatted += `Round ID: ${ballot.round_id}\n`
89+
formatted += `Status: ${ballot.status}\n`
90+
formatted += `Budget: ${ballot.budget}\n`
91+
formatted += `Created At: ${ballot.created_at}\n`
92+
formatted += `Updated At: ${ballot.updated_at}\n`
93+
formatted += `Published At: ${ballot.published_at}\n\n`
94+
95+
if (Array.isArray(ballot.category_allocations)) {
96+
formatted += 'Category Allocations:\n'
97+
ballot.category_allocations.forEach((cat) => {
98+
formatted += ` - Category Slug: ${cat.category_slug}, Allocation: ${cat.allocation}, Locked: ${cat.locked}\n`
99+
})
100+
formatted += '\n'
101+
}
102+
103+
if (Array.isArray(ballot.projects_allocations)) {
104+
formatted += 'Projects Allocations:\n'
105+
ballot.projects_allocations.forEach((proj) => {
106+
formatted += ` - Project ID: ${proj.project_id}, Name: ${proj.name}, Image: ${proj.image}, Position: ${proj.position}, Allocation: ${proj.allocation}, Impact: ${proj.impact}\n`
107+
})
108+
formatted += '\n'
109+
}
110+
111+
if (Array.isArray(ballot.projects_to_be_evaluated)) {
112+
formatted += 'Projects to be Evaluated:\n'
113+
ballot.projects_to_be_evaluated.forEach((project) => {
114+
formatted += ` - ${project}\n`
115+
})
116+
formatted += '\n'
117+
}
118+
119+
if (ballot.payload_for_signature) {
120+
formatted += 'Payload for Signature:\n'
121+
formatted += ` Budget: ${ballot.payload_for_signature.budget}\n`
122+
formatted += ` Category Allocation: ${JSON.stringify(ballot.payload_for_signature.category_allocation, null, 2)}\n`
123+
formatted += ` Projects Allocation: ${JSON.stringify(ballot.payload_for_signature.projects_allocation, null, 2)}\n`
124+
}
125+
126+
return formatted
127+
}

packages/ethernaut-optigov/src/tasks/Delegates.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const types = require('ethernaut-common/src/validation/types')
22
const output = require('ethernaut-common/src/ui/output')
33
const Delegates = require('../internal/agora/Delegates')
4-
const Agora = require('../internal/agora/Agora')
4+
const agora = require('../internal/agora/agoraInstance')
55

66
const RELATED_DATA = {
77
votes: 'votes',
@@ -40,7 +40,6 @@ require('../scopes/optigov')
4040
)
4141
.setAction(async ({ limit, offset, address, relatedData }) => {
4242
try {
43-
const agora = new Agora()
4443
const delegates = new Delegates(agora)
4544

4645
if (address) {
@@ -78,7 +77,6 @@ require('../scopes/optigov')
7877

7978
// If no specific address or ENS is given, fetch the list of delegates
8079
const delegateList = await delegates.getDelegates({ limit, offset })
81-
8280
return output.resultBox(printDelegates(delegateList), 'Delegates')
8381
} catch (err) {
8482
return output.errorBox(err)

packages/ethernaut-optigov/src/tasks/Projects.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const types = require('ethernaut-common/src/validation/types')
22
const output = require('ethernaut-common/src/ui/output')
33
const Projects = require('../internal/agora/Projects')
44
const Rounds = require('../internal/agora/Rounds')
5-
const Agora = require('../internal/agora/Agora')
5+
const agora = require('../internal/agora/agoraInstance')
66

77
require('../scopes/optigov')
88
.task(
@@ -81,7 +81,6 @@ function printProjects(projects) {
8181
}
8282

8383
async function getProjects(limit, offset, round) {
84-
const agora = new Agora()
8584
const projects = new Projects(agora)
8685
const rounds = new Rounds(agora)
8786

packages/ethernaut-optigov/src/tasks/Proposals.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const types = require('ethernaut-common/src/validation/types')
22
const output = require('ethernaut-common/src/ui/output')
33
const Proposals = require('../internal/agora/Proposals')
4-
const Agora = require('../internal/agora/Agora')
4+
const agora = require('../internal/agora/agoraInstance')
55

66
const VOTES = {
77
yes: 'yes',
@@ -40,7 +40,6 @@ require('../scopes/optigov')
4040
.setAction(async ({ limit, offset, proposalId, votes }) => {
4141
try {
4242
// Instantiate Agora and Proposals
43-
const agora = new Agora()
4443
const proposals = new Proposals(agora)
4544

4645
// If proposalId is provided, fetch specific proposal or votes

packages/ethernaut-optigov/src/tasks/Rounds.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const types = require('ethernaut-common/src/validation/types')
22
const output = require('ethernaut-common/src/ui/output')
33
const Rounds = require('../internal/agora/Rounds')
4-
const Agora = require('../internal/agora/Agora')
4+
const agora = require('../internal/agora/agoraInstance')
55

66
const LATEST = {
77
yes: 'yes',
@@ -39,7 +39,6 @@ require('../scopes/optigov')
3939
)
4040
.setAction(async ({ limit, offset, roundId, latest }) => {
4141
try {
42-
const agora = new Agora()
4342
const rounds = new Rounds(agora)
4443

4544
if (latest === LATEST.yes) {

packages/ethernaut-optigov/src/tasks/login.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const output = require('ethernaut-common/src/ui/output')
22
const { createSiweMessage } = require('../internal/Siwe')
33
const EthernautCliError = require('ethernaut-common/src/error/error')
44
const Auth = require('../internal/agora/Auth')
5-
const Agora = require('../internal/agora/Agora')
5+
const agora = require('../internal/agora/agoraInstance')
66

77
require('../scopes/optigov')
88
.task(
@@ -25,7 +25,6 @@ require('../scopes/optigov')
2525
'ethernaut-optigov',
2626
)
2727

28-
const agora = new Agora()
2928
const auth = new Auth(agora)
3029

3130
const statement = 'Log in to Agoras RetroPGF API with SIWE.'
@@ -39,7 +38,7 @@ require('../scopes/optigov')
3938

4039
await auth.authenticateWithAgora(preparedMessage, signature, nonce)
4140

42-
return output.resultBox(`Logged in with address: ${signer.address})`)
41+
return output.resultBox(`Logged in with address: ${signer.address}`)
4342
} catch (err) {
4443
return output.errorBox(err)
4544
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const types = require('ethernaut-common/src/validation/types')
2+
const output = require('ethernaut-common/src/ui/output')
3+
const storage = require('ethernaut-common/src/io/storage')
4+
const { setEnvVar } = require('ethernaut-common/src/io/env')
5+
6+
require('../scopes/optigov')
7+
.task('agorakey', 'Sets the Agora API Key')
8+
.addParam('apiKey', 'The Agora API Key to use', undefined, types.string)
9+
.setAction(async ({ apiKey }, _hre) => {
10+
try {
11+
const config = storage.readConfig()
12+
13+
let summary = []
14+
15+
if (apiKey) {
16+
const currentKey = process.env.AGORA_API_KEY
17+
setEnvVar('AGORA_API_KEY', apiKey)
18+
summary.push(`- Agora API Key set to ${apiKey} (was ${currentKey})`)
19+
summary.push(
20+
'Please restart the tool for the new API key to take effect.',
21+
)
22+
}
23+
24+
storage.saveConfig(config)
25+
26+
if (summary.length === 0) {
27+
summary.push('No changes')
28+
}
29+
30+
return output.resultBox(summary.join('\n'))
31+
} catch (err) {
32+
return output.errorBox(err)
33+
}
34+
})

0 commit comments

Comments
 (0)