-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Auto Report Generation #19
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Proposal <proposal_id>. <proposal_title> | ||
|
||
### Voting Link | ||
[Link to voting page](<voting_link>) | ||
|
||
### Governance Forum Discussions | ||
[Link to forum discussions](<gov_forum_link>) | ||
|
||
### Payloads | ||
<loop:chain,payload_link> * <chain> - [proposal payloads](<payload_link>) </loop> | ||
|
||
|
||
## Certora Analysis | ||
|
||
### Proposal Types | ||
{**TODO: Choose types from the following list.**} | ||
* :scroll: :small_red_triangle: Contract upgrades | ||
* :moneybag: :receipt: Asset transfers | ||
* :handshake: Permission granting and revoking | ||
* :wrench: :bar_chart: Configuration updates | ||
* :gem: :new: Listing new assets | ||
|
||
### Context | ||
{**TODO: Write context.**} | ||
|
||
### Proposal Creation | ||
Transaction: [<transaction_hash>](<transaction_link>) | ||
``` | ||
<transaction_data> | ||
``` | ||
|
||
**`createProposal()` Parameters** | ||
``` | ||
<createProposal_parameters_data> | ||
``` | ||
|
||
### Aave Seatbelt Report | ||
|
||
**Proposal Report** | ||
[Link to Seatbelt proposal report](<seatbelt_link>) | ||
|
||
**Payload Reports** | ||
<loop:chain,payload_seatbelt_link> * <chain> - [payload Seatbelt report](<payload_seatbelt_link>) </loop> | ||
|
||
### Technical Analysis | ||
{**TODO: Write technical analysis.**} | ||
|
||
The proposal is consistent with the description on both Snapshot and the governance forum. | ||
|
||
### Certora validations | ||
* :white_check_mark: The code on the proposal payload corresponds to the proposal specification. | ||
* :white_check_mark: The proposal includes a proper tests suite, checking all necessary post-conditions. | ||
* :white_check_mark: BGD reviewed the payload before the proposal was submitted. | ||
* :white_check_mark: Certora reviewed the procedure followed to submit the proposal. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from Quorum.auto_report.report_generator import ReportGenerator | ||
from Quorum.auto_report.tags import AaveTags | ||
|
||
import argparse | ||
from pathlib import Path | ||
|
||
|
||
DEFAULT_TEMPLATE_PATH = Path(__file__).parent / 'AaveReportTemplate.md' | ||
|
||
|
||
def parse_args() -> argparse.Namespace: | ||
parser = argparse.ArgumentParser(description='This tool generates automatic proposal reports.') | ||
parser.add_argument('--proposal_id', required=True, type=int, help='The proposal id to generate report to.') | ||
parser.add_argument('--template', default=DEFAULT_TEMPLATE_PATH, help='The report template to use.') | ||
|
||
args = parser.parse_args() | ||
|
||
if not Path(args.template).exists(): | ||
raise FileNotFoundError(f'could not find template at {args.template}.') | ||
|
||
return parser.parse_args() | ||
|
||
|
||
def main(): | ||
args = parse_args() | ||
|
||
with open(args.template) as f: | ||
template = f.read() | ||
|
||
report = ReportGenerator(template, AaveTags(args.proposal_id).tag_mappings).report | ||
|
||
with open(f'v3-{args.proposal_id}-<title>.md', 'w') as f: | ||
f.write(report) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() | ||
Comment on lines
+24
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Must have Docs both here and in the general readme file.
Comment on lines
+24
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the project logger here. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import re | ||
|
||
|
||
class ReportGenerator: | ||
LOOP_COMMAND = 'loop' | ||
|
||
def __init__(self, template: str, tag_mappings: dict[str, str | list[str]]) -> None: | ||
self.report = self.__generate_report(template, tag_mappings) | ||
|
||
@staticmethod | ||
def __generate_report(template: str, tag_mappings: dict[str, str | list[str]]) -> str: | ||
res = ReportGenerator.__replace_loops(template, tag_mappings) | ||
res = ReportGenerator.__fill_tags(res, tag_mappings) | ||
return res | ||
|
||
@staticmethod | ||
def __fill_tags(template: str, tag_mappings: dict[str, str | list[str]]) -> str: | ||
res = template | ||
for tag, value in tag_mappings.items(): | ||
if isinstance(value, str): | ||
res = res.replace(f'<{tag}>', value) | ||
return res | ||
|
||
@staticmethod | ||
def __replace_loops(template: str, tag_mappings: dict[str, str | list[str]]) -> str: | ||
pattern = rf'<{ReportGenerator.LOOP_COMMAND}:([^>]+)>' | ||
matches = re.findall(pattern, template) | ||
res = template | ||
for m in matches: | ||
loop_tag_mapping = {tag: tag_mappings[tag] for tag in m.split(',')} | ||
res = ReportGenerator.__unroll_loop(res, m, loop_tag_mapping) | ||
return res | ||
|
||
@staticmethod | ||
def __unroll_loop(template: str, looping_tags: str, loop_tag_mappings: dict[str, list[str]]) -> str: | ||
start_tag = f'<{ReportGenerator.LOOP_COMMAND}:{looping_tags}>' | ||
end_tag = f'</{ReportGenerator.LOOP_COMMAND}>' | ||
|
||
start_index = template.index(start_tag) + len(start_tag) | ||
end_index = template.index(end_tag) | ||
|
||
loop_template = template[start_index:end_index].strip() | ||
|
||
loop_result = ReportGenerator.__build_loop_content(loop_template, loop_tag_mappings) | ||
|
||
return template[:template.index(start_tag)] + loop_result + template[end_index + len(end_tag):] | ||
|
||
@staticmethod | ||
def extract_loop_values_lengths(values: list[list[str]]) -> int: | ||
expected_length = len(values[0]) | ||
for v in values: | ||
if len(v) != expected_length: | ||
raise ValueError('lengths not equal') | ||
return expected_length | ||
|
||
@staticmethod | ||
def __build_loop_content(loop_template: str, loop_tag_mappings: dict[str, list[str]]) -> str: | ||
loop_count = ReportGenerator.extract_loop_values_lengths(list(loop_tag_mappings.values())) | ||
res = '' | ||
for i in range(loop_count): | ||
res += ReportGenerator.__fill_tags(loop_template, | ||
{tag: values[i] for tag, values in loop_tag_mappings.items()}) + '\n' | ||
return res.strip() | ||
Comment on lines
+4
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add Docs to all methods in the class |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
class AaveTags: | ||
def __init__(self, proposal_id: int) -> None: | ||
self.proposal_id = proposal_id | ||
self.tag_mappings = { | ||
'proposal_id': str(proposal_id), | ||
'proposal_title': self.get_proposal_title(), | ||
'chain': self.get_chains(), | ||
'payload_link': self.get_payload_links(), | ||
'transaction_hash': self.get_transaction_hash(), | ||
'transaction_data': self.get_transaction_data(), | ||
'createProposal_parameters_data': self.get_create_func_parameters_data(), | ||
'seatbelt_link': self.get_seatbelt_link(), | ||
'payload_seatbelt_link': self.get_seatbelt_payload_links() | ||
} | ||
|
||
def get_proposal_title(self) -> str: | ||
return 'titular' | ||
|
||
def get_chains(self) -> list[str]: | ||
return ['btc', 'eth', 'sol'] | ||
|
||
|
||
def get_payload_links(self) -> list[str]: | ||
return ['btc_link', 'eth_link', 'sol_link'] | ||
|
||
|
||
def get_transaction_hash(self) -> str: | ||
return 'hush baby' | ||
|
||
|
||
def get_transaction_link(self) -> str: | ||
return 'www.transaction.link.com' | ||
|
||
|
||
def get_transaction_data(self) -> str: | ||
return ('Some transaction data \n' | ||
'Like this \n' | ||
'And this') | ||
|
||
|
||
def get_create_func_parameters_data(self) -> str: | ||
return ('A\n' | ||
'long\n' | ||
'list\n' | ||
'of\n' | ||
'parameters\n' | ||
'data') | ||
|
||
|
||
def get_seatbelt_link(self) -> str: | ||
return 'www.seatbelt.com' | ||
|
||
|
||
def get_seatbelt_payload_links(self) -> list[str]: | ||
return ['www.seatbelt.btc', 'www.seatbelt.eth', 'www.seatbelt.sol'] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
20241110.154442.714477 | ||
20241124.092904.107425 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldnt be the proposal_title field instead of -<title>.md?