diff --git a/Quorum/auto_report/AaveReportTemplate.md b/Quorum/auto_report/AaveReportTemplate.md new file mode 100644 index 0000000..ed7e285 --- /dev/null +++ b/Quorum/auto_report/AaveReportTemplate.md @@ -0,0 +1,54 @@ +# Proposal . + +### Voting Link +[Link to voting page]() + +### Governance Forum Discussions +[Link to forum discussions]() + +### Payloads + * - [proposal payloads]() + + +## 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: []() +``` + +``` + +**`createProposal()` Parameters** +``` + +``` + +### Aave Seatbelt Report + +**Proposal Report** +[Link to Seatbelt proposal report]() + +**Payload Reports** + * - [payload Seatbelt report]() + +### 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. \ No newline at end of file diff --git a/Quorum/auto_report/create_report.py b/Quorum/auto_report/create_report.py new file mode 100644 index 0000000..311281a --- /dev/null +++ b/Quorum/auto_report/create_report.py @@ -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}-.md', 'w') as f: + f.write(report) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Quorum/auto_report/report_generator.py b/Quorum/auto_report/report_generator.py new file mode 100644 index 0000000..73121ff --- /dev/null +++ b/Quorum/auto_report/report_generator.py @@ -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() \ No newline at end of file diff --git a/Quorum/auto_report/tags.py b/Quorum/auto_report/tags.py new file mode 100644 index 0000000..3bc0d9e --- /dev/null +++ b/Quorum/auto_report/tags.py @@ -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'] \ No newline at end of file diff --git a/version b/version index 85dcb05..7146e52 100644 --- a/version +++ b/version @@ -1 +1 @@ -20241110.154442.714477 +20241124.092904.107425