diff --git a/.github/PULL_REQUEST_TEMPLATE/0_proposal.md b/.github/PULL_REQUEST_TEMPLATE/0_proposal.md new file mode 100644 index 0000000..9474eb7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/0_proposal.md @@ -0,0 +1,20 @@ +--- +name: 🚀 Proposal +about: Propose a new ARC +title: "[Proposal] ARC-XXXX: Insert Title Here" +labels: 'status-draft' +--- +**Discussion Thread:** _TODO. Insert a link to the corresponding discussion thread here._ + + +## 🚀 Proposal + + +## Voting History +- None yet. + +## Notes to Reviewers + + + + diff --git a/.github/PULL_REQUEST_TEMPLATE/1_update.md b/.github/PULL_REQUEST_TEMPLATE/1_update.md new file mode 100644 index 0000000..8738d39 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/1_update.md @@ -0,0 +1,10 @@ +--- +name: 🔨Update +about: Update an existing ARC. +title: "[Update] ARC-XXXX: Insert Title Here" +labels: 'update' +--- + +## 🔨 Update + + diff --git a/.github/PULL_REQUEST_TEMPLATE/config.yml b/.github/PULL_REQUEST_TEMPLATE/config.yml new file mode 100644 index 0000000..59699e0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: ❓ Q&A + url: https://discord.gg/aleo + about: For questions or technical troubleshooting, please ask them in the Aleo Discord. diff --git a/.github/workflows/markdown.yml b/.github/workflows/archive/markdown.yml similarity index 100% rename from .github/workflows/markdown.yml rename to .github/workflows/archive/markdown.yml diff --git a/README.md b/README.md index d1e54f7..2b70e9a 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,29 @@ Aleo Request for Comments (ARCs) are protocol-level, network-level, and applicat ## ✍️ Getting Started To create a new ARC proposal: -1. Copy template [ARC-0000](./arc-0000) into a new folder with the latest ARC number (ARC-XXXX) -2. Open a [Github Discussion](https://github.com/AleoHQ/ARCs/discussions/categories/arcs) for your ARC number -3. File a [Pull Request](https://github.com/AleoHQ/ARCs/pulls) to add your new ARC proposal +1. Open a [Github Discussion](https://github.com/AleoHQ/ARCs/discussions/categories/arcs) with your proposal using template [ARC-0000](./arc-0000) and an available ARC number. +2. File a [Pull Request](https://github.com/AleoHQ/ARCs/pulls) with your proposal in a new subdirectory. + +### Progressing an ARC + +An ARC will start as a "Draft" and progress through the following stages: + +Once a proposal is up: +1. The community will discuss and review the proposal. A maintainer will monitor the ARC and change its status to "Active" once it is ready. + a. ARCs will be prioritized by number of votes and whether a prototype exists. + b. ARCs will be discussed during certain community calls. Proposers will have the opportunity to join and participate in the discussion. + c. Up to this point, the ARC can be withdrawn by the proposer or withdrawn by the maintainers if there is no activity for a long time. +2. A governor or a team member of the Aleo Network Foundation (ANF) will create a formal proposal on Aleo governance (https://vote.aleo.org/) and initiate the voting process. +3. The community will vote on the proposal for approval. +4. If the proposal is accepted, its status will be updated to "Accepted" and the associated pull request will be merged into the ARCs repo. If the proposal is rejected, the status will be reverted to "Draft". +5. The relevant parties should complete the implementation. Updates can be made to the ARC as needed through new PRs, which do not need votes. +6. Once the implementation is finalized, the status will change from "Accepted" to "Final" or "Living", depending on the nature of the proposal. The associated discussion will be closed. + +A proposal can be "Deprecated" if it is replaced by a new proposal. + +### Statuses + +See [ARC-0001](./arc-0001) for a detailed explanation of the statuses. ## 📜 License diff --git a/arc-0000/README.md b/arc-0000/README.md index d165ee1..870e13b 100644 --- a/arc-0000/README.md +++ b/arc-0000/README.md @@ -1,9 +1,9 @@ --- -arc: 0000 # Add the next sequence number +arc: 0 # Add an unused ARC number. **DO NOT ADD LEADING ZEROS.** title: # Title authors: # Add all Github usernames, emails, and/or full names discussion: # Create a 'Github Discussion' titled 'ARC-XXXX: {TITLE}` -topic: # Choose: Protocol, Network, or Application +topic: # Choose: Protocol, Network, Application, or Meta status: Draft created: # Date --- diff --git a/arc-0001/README.md b/arc-0001/README.md index d2f01e0..60753d1 100644 --- a/arc-0001/README.md +++ b/arc-0001/README.md @@ -29,14 +29,13 @@ graph LR end draft([Draft]) -.-> withdrawn([Withdrawn]) - active([Active]) -.-> withdrawn([Withdrawn]) subgraph standards active([Active]) --> decision{Decision} decision{Decision} --> accepted([Accepted]) end - decision{Decision} -.-> rejected([Rejected]) + decision{Decision} -.-> rejected([Draft]) subgraph standards accepted([Accepted]) --> final([Final]) @@ -49,11 +48,9 @@ graph LR `Active` refers to a proposal with a reference implementation that is ready for review. -`Withdrawn` refers to a proposal that was previously marked as `Idea`, `Draft`, or `Active`. +`Withdrawn` refers to a proposal that was previously marked as `Draft`, or `Active`. -`Accepted` refers to a proposal that was `Active` with a reference implementation, and has been approved by reviewer(s). - -`Rejected` refers to a proposal that was `Active` with a reference implementation, and has been denied by reviewer(s). +`Accepted` refers to a proposal that has been approved by the community and is ready for implementation. `Final` refers to a proposal that was `Accepted`, and the reference implementation has been incorporated into Aleo. diff --git a/arc-0002/README.md b/arc-0002/README.md index 0598a33..4649683 100644 --- a/arc-0002/README.md +++ b/arc-0002/README.md @@ -1,10 +1,10 @@ --- -arc: 2 +arc: 0002 title: Aleo Virtual Machine (AVM) authors: howardwu, acoglio, collinc97, d0cd discussion: https://github.com/AleoHQ/ARCs/discussions/5 topic: Protocol -status: Draft +status: Living created: 2020-02-07 --- diff --git a/arc-0004/README.md b/arc-0004/README.md deleted file mode 100644 index 05656f6..0000000 --- a/arc-0004/README.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -arc: 4 -title: Flagged Operations -authors: bendyarm, d0cd, acoglio -discussion: https://github.com/AleoHQ/ARCs/discussions/63 -topic: # Choose: Protocol, Network, or Application -status: Draft -created: # Date ---- - -## Abstract - -Some Aleo instructions can halt when passed certain arguments. -This halting behavior makes those instructions unusable -by a program that wants to try something else if a halt is detected. - -For example, if a Leo program contains a conditional not in a -finalize, both branches of the conditional are executed in the -circuit, so if a branch not taken executes an instruction that halts, -the whole program will incorrectly halt. Replacing the halting -instruction by one that returns an error flag will allow such a -program to compile correctly. - -This ARC proposes to add new opcodes for flagged operations -corresponding to the Aleo Instructions that can halt. - -## Specification - -Each flagged operation is identical to the current halting instruction -except it doesn't halt and it returns another return value that is a -boolean. - -The flagged operations are different from wrapped (e.g. `abs.w`) or -lossy (e.g. `cast.lossy`) operations. It is important that the flagged -operation have the same semantics as the current halting instruction -except for the halting behavior and extra return value, for ease of -use by compilers. - -| Current Halting Opcode | New Flagged Opcode | -|:-------------------:|:-----------------------:| -| abs | abs.flagged | -| add | add.flagged | -| cast | cast.flagged | -| commit.bhp256 | commit.bhp256.flagged | -| commit.bhp512 | commit.bhp512.flagged | -| commit.bhp768 | commit.bhp768.flagged | -| commit.bhp768 | commit.bhp1024.flagged | -| commit.ped64 | commit.ped64.flagged | -| commit.ped128 | commit.ped128.flagged | -| div | div.flagged | -| hash.bhp256 | hash.bhp256.flagged | -| hash.bhp512 | hash.bhp512.flagged | -| hash.bhp768 | hash.bhp768.flagged | -| hash.bhp1024 | hash.bhp1024.flagged | -| hash.ped64 | hash.ped64.flagged | -| hash.ped128 | hash.ped128.flagged | -| inv | inv.flagged | -| mod | mod.flagged | -| mul | mul.flagged | -| neg | neg.flagged | -| pow | pow.flagged | -| rem | rem.flagged | -| shl | shl.flagged | -| shr | shr.flagged | -| sqrt | sqrt.flagged | -| sub | sub.flagged | - -### Test Cases - -This section should introduce any and all critical test cases that need to be considered for the specification. - -The following note applies to all tests of Aleo Instructions. For all tests, both literal constant arguments -and variable arguments should be tested. If there are two arguments, we need to test all four -combinations. This is because the Aleo Instructions compiler generates more optimized code for literal -constant arguments, and the circuits can differ substantially. - -One or more arguments that causes halting for a current halting opcode should be used as an input -for the equivalent new flagged opcode to make sure it doesn't halt. - -An assortment of arguments that do not cause halting for a current halting opcode should be -used as input to both halting and flagged operations to make sure they return the same value -(other than the halting flag, of course). - -## Dependencies - -The main change is to the snarkVM repository. - -The Aleo Explorer will need to be updated to output the new operations when displaying an aleo program. - -The "sdk" repository may need to be changed to show syntax highlighting on the new opcodes. - -After this change, the Leo compiler can more easily be fixed to prevent the [conditional halting bug.]() - -### Backwards Compatibility - -This change is strictly additive. - -## Security & Compliance - -This change does not affect security or regulatory issues. - -## References - -Leo bug: -https://github.com/AleoHQ/leo/issues/27482 diff --git a/arc-0021/README.md b/arc-0021/README.md new file mode 100644 index 0000000..8c32d30 --- /dev/null +++ b/arc-0021/README.md @@ -0,0 +1,793 @@ +--- +arc: 21 +title: Multi-Token Standard Program +authors: evanmarshall +discussion: N/A (Retroactive Proposal) +topic: Application +status: Deprecated +created: 2024-06-07 +--- + +## Abstract + +The current version of SnarkOS/SnarkVM does not support either dynamic contract calls ("call by address") or standard interfaces. This makes composability difficult to implement. While it is possible for a first contract to call a second contract, the first contract must be explicitly compiled with knowledge of the code of the second contract. In the DeFi example given above, this means that a DeFi contract must be compiled with support for all token contracts that it will ever interact with. If a new token contract is subsequently deployed on-chain, the DeFi contract will need to be re-compiled and redeployed on chain in order to interact with that token. Proposal. We propose a single multi-token support program (MTSP) that can manage balances for many different ARC-20 tokens. This program would be the standard "hub" that all tokens and DeFi contracts interface with. Individual ARC-20 tokens can register with the MTSP and mint new tokens via this contract. Transfers of token value will occur by direct call to the MTSP rather than the ARC-20 contract itself. The benefit of this approach is that DeFi contracts do not need to be compiled with any special knowledge of individual ARC-20 tokens: their sole dependency will be the MTSP. Hence the deployment of new tokens does not require re-deployment of DeFi contracts. Similarly, individual ARC-20 tokens can also be compiled with dependence on the MTSP, but no dependence on the DeFi programs. The MTSP thus allows interoperability between new tokens and DeFi contracts, with no need for contract re-deployment. As a secondary benefit, the MTSP will provide privacy benefits (via an improved anonymity set) because all private transfers within the MTSP will conceal the identity of the specific token being transferred. + +## Source And Examples + +You can find the source [here](https://github.com/demox-labs/aleo-standard-programs/blob/8bcc805da1399bd35c713155edeceee0096a7d31/pondo-bot/old_programs/mtsp_credits.aleo#L4) . We have also deployed examples of usage of the MTSP in the same repository as well as deployed the program to Testnet Beta in this transaction: at138kpz0xdfzqavxcnku52954ysx2sptmr263precczclahts8csgs4l05ah + +A leo program is provided by due to limitations at the time of writing in Leo, we have to make some manual edits to the Aleo opcodes so the full Aleo source is provided below. + +## Specification +``` +program multi_token_support_program_v1.aleo; + + +record Token: +owner as address.private; +amount as u128.private; +token_id as field.private; +external_authorization_required as boolean.private; +authorized_until as u32.private; + +struct TokenMetadata: +token_id as field; +name as u128; +symbol as u128; +decimals as u8; +supply as u128; +max_supply as u128; +admin as address; +external_authorization_required as boolean; +external_authorization_party as address; + +struct TokenOwner: +account as address; +token_id as field; + +struct Balance: +token_id as field; +account as address; +balance as u128; +authorized_until as u32; + +struct Allowance: +account as address; +spender as address; +token_id as field; + + +mapping registered_tokens: +key as field.public; +value as TokenMetadata.public; + + +mapping balances: +key as field.public; +value as Balance.public; + + +mapping authorized_balances: +key as field.public; +value as Balance.public; + + +mapping allowances: +key as field.public; +value as u128.public; + + +mapping roles: +key as field.public; +value as u8.public; + + +function transfer_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +async transfer_public r0 r1 r2 self.caller into r3; +output r3 as multi_token_support_program_v1.aleo/transfer_public.future; + +finalize transfer_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +input r3 as address.public; +cast r3 r0 into r4 as TokenOwner; +hash.bhp256 r4 into r5 as field; +get authorized_balances[r5] into r6; +get registered_tokens[r0] into r7; +lte block.height r6.authorized_until into r8; +not r7.external_authorization_required into r9; +or r8 r9 into r10; +assert.eq r10 true; +sub r6.balance r2 into r11; +cast r0 r3 r11 r6.authorized_until into r12 as Balance; +set r12 into authorized_balances[r5]; +cast r1 r0 into r13 as TokenOwner; +hash.bhp256 r13 into r14 as field; +get registered_tokens[r0] into r15; +ternary r15.external_authorization_required 0u32 4294967295u32 into r16; +cast r0 r1 0u128 r16 into r17 as Balance; +get.or_use balances[r14] r17 into r18; +get.or_use authorized_balances[r14] r17 into r19; +ternary r15.external_authorization_required r18.token_id r19.token_id into r20; +ternary r15.external_authorization_required r18.account r19.account into r21; +ternary r15.external_authorization_required r18.balance r19.balance into r22; +ternary r15.external_authorization_required r18.authorized_until r19.authorized_until into r23; +cast r20 r21 r22 r23 into r24 as Balance; +add r24.balance r2 into r25; +cast r0 r1 r25 r24.authorized_until into r26 as Balance; +branch.eq r15.external_authorization_required false to end_then_0_0; +set r26 into balances[r14]; +branch.eq true true to end_otherwise_0_1; +position end_then_0_0; +set r26 into authorized_balances[r14]; +position end_otherwise_0_1; + + + + +function transfer_public_as_signer: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +async transfer_public_as_signer r0 r1 r2 self.signer into r3; +output r3 as multi_token_support_program_v1.aleo/transfer_public_as_signer.future; + +finalize transfer_public_as_signer: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +input r3 as address.public; +cast r3 r0 into r4 as TokenOwner; +hash.bhp256 r4 into r5 as field; +get authorized_balances[r5] into r6; +get registered_tokens[r0] into r7; +lte block.height r6.authorized_until into r8; +not r7.external_authorization_required into r9; +or r8 r9 into r10; +assert.eq r10 true; +sub r6.balance r2 into r11; +cast r0 r3 r11 r6.authorized_until into r12 as Balance; +set r12 into authorized_balances[r5]; +cast r1 r0 into r13 as TokenOwner; +hash.bhp256 r13 into r14 as field; +get registered_tokens[r0] into r15; +ternary r15.external_authorization_required 0u32 4294967295u32 into r16; +cast r0 r1 0u128 r16 into r17 as Balance; +get.or_use balances[r14] r17 into r18; +get.or_use authorized_balances[r14] r17 into r19; +ternary r15.external_authorization_required r18.token_id r19.token_id into r20; +ternary r15.external_authorization_required r18.account r19.account into r21; +ternary r15.external_authorization_required r18.balance r19.balance into r22; +ternary r15.external_authorization_required r18.authorized_until r19.authorized_until into r23; +cast r20 r21 r22 r23 into r24 as Balance; +add r24.balance r2 into r25; +cast r0 r1 r25 r24.authorized_until into r26 as Balance; +branch.eq r15.external_authorization_required false to end_then_0_2; +set r26 into balances[r14]; +branch.eq true true to end_otherwise_0_3; +position end_then_0_2; +set r26 into authorized_balances[r14]; +position end_otherwise_0_3; + + + + +function transfer_private: +input r0 as address.private; +input r1 as u128.private; +input r2 as Token.record; +sub r2.amount r1 into r3; +cast r2.owner r3 r2.token_id r2.external_authorization_required r2.authorized_until into r4 as Token.record; +ternary r2.external_authorization_required 0u32 4294967295u32 into r5; +cast r0 r1 r2.token_id r2.external_authorization_required r5 into r6 as Token.record; +async transfer_private r2.external_authorization_required r2.authorized_until into r7; +output r4 as Token.record; +output r6 as Token.record; +output r7 as multi_token_support_program_v1.aleo/transfer_private.future; + +finalize transfer_private: +input r0 as boolean.public; +input r1 as u32.public; +lte block.height r1 into r2; +not r0 into r3; +or r2 r3 into r4; +assert.eq r4 true; + + + + +function transfer_private_to_public: +input r0 as address.public; +input r1 as u128.public; +input r2 as Token.record; +sub r2.amount r1 into r3; +cast r2.owner r3 r2.token_id r2.external_authorization_required r2.authorized_until into r4 as Token.record; +async transfer_private_to_public r2.token_id r0 r1 r2.authorized_until r2.external_authorization_required into r5; +output r4 as Token.record; +output r5 as multi_token_support_program_v1.aleo/transfer_private_to_public.future; + +finalize transfer_private_to_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +input r3 as u32.public; +input r4 as boolean.public; +lte block.height r3 into r5; +not r4 into r6; +or r5 r6 into r7; +assert.eq r7 true; +cast r1 r0 into r8 as TokenOwner; +hash.bhp256 r8 into r9 as field; +get registered_tokens[r0] into r10; +ternary r10.external_authorization_required 0u32 4294967295u32 into r11; +cast r0 r1 0u128 r11 into r12 as Balance; +get.or_use balances[r9] r12 into r13; +get.or_use authorized_balances[r9] r12 into r14; +ternary r10.external_authorization_required r13.token_id r14.token_id into r15; +ternary r10.external_authorization_required r13.account r14.account into r16; +ternary r10.external_authorization_required r13.balance r14.balance into r17; +ternary r10.external_authorization_required r13.authorized_until r14.authorized_until into r18; +cast r15 r16 r17 r18 into r19 as Balance; +add r19.balance r2 into r20; +cast r0 r1 r20 r19.authorized_until into r21 as Balance; +branch.eq r10.external_authorization_required false to end_then_0_4; +set r21 into balances[r9]; +branch.eq true true to end_otherwise_0_5; +position end_then_0_4; +set r21 into authorized_balances[r9]; +position end_otherwise_0_5; + +function transfer_public_to_private: +input r0 as field.public; +input r1 as address.private; +input r2 as u128.public; +input r3 as boolean.public; +ternary r3 0u32 4294967295u32 into r4; +cast r1 r2 r0 r3 r4 into r5 as Token.record; +async transfer_public_to_private r0 r2 self.caller r3 into r6; +output r5 as Token.record; +output r6 as multi_token_support_program_v1.aleo/transfer_public_to_private.future; + +finalize transfer_public_to_private: +input r0 as field.public; +input r1 as u128.public; +input r2 as address.public; +input r3 as boolean.public; +get registered_tokens[r0] into r4; +assert.eq r4.external_authorization_required r3; +cast r2 r0 into r5 as TokenOwner; +hash.bhp256 r5 into r6 as field; +get authorized_balances[r6] into r7; +get registered_tokens[r0] into r8; +lte block.height r7.authorized_until into r9; +not r8.external_authorization_required into r10; +or r9 r10 into r11; +assert.eq r11 true; +sub r7.balance r1 into r12; +cast r0 r2 r12 r7.authorized_until into r13 as Balance; +set r13 into authorized_balances[r6]; + +function initialize: +async initialize into r0; +output r0 as multi_token_support_program_v1.aleo/initialize.future; + +finalize initialize: +cast 3443843282313283355522573239085696902919850365217539366784739393210722344986field 1095517519u128 1095517519u128 6u8 1_500_000_000_000_000u128 1_500_000_000_000_000u128 multi_token_support_program_v1.aleo false multi_token_support_program_v1.aleo into r0 as TokenMetadata; +set r0 into registered_tokens[3443843282313283355522573239085696902919850365217539366784739393210722344986field]; + +function register_token: +input r0 as field.public; +input r1 as u128.public; +input r2 as u128.public; +input r3 as u8.public; +input r4 as u128.public; +input r5 as boolean.public; +input r6 as address.public; +is.neq r0 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r7; +assert.eq r7 true; +cast r0 r1 r2 r3 0u128 r4 self.caller r5 r6 into r8 as TokenMetadata; +async register_token r8 into r9; +output r9 as multi_token_support_program_v1.aleo/register_token.future; + +finalize register_token: +input r0 as TokenMetadata.public; +contains registered_tokens[r0.token_id] into r1; +assert.eq r1 false; +set r0 into registered_tokens[r0.token_id]; + +function update_token_management: +input r0 as field.public; +input r1 as address.public; +input r2 as address.public; +is.neq r0 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r3; +assert.eq r3 true; +async update_token_management r0 r1 r2 self.caller into r4; +output r4 as multi_token_support_program_v1.aleo/update_token_management.future; + +finalize update_token_management: +input r0 as field.public; +input r1 as address.public; +input r2 as address.public; +input r3 as address.public; +get registered_tokens[r0] into r4; +assert.eq r3 r4.admin; +cast r0 r4.name r4.symbol r4.decimals r4.supply r4.max_supply r1 r4.external_authorization_required r2 into r5 as TokenMetadata; +set r5 into registered_tokens[r0]; + + +function set_role: +input r0 as field.public; +input r1 as address.public; +input r2 as u8.public; +is.neq r0 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r3; +assert.eq r3 true; +async set_role r0 r1 r2 self.caller into r4; +output r4 as multi_token_support_program_v1.aleo/set_role.future; + +finalize set_role: +input r0 as field.public; +input r1 as address.public; +input r2 as u8.public; +input r3 as address.public; +get registered_tokens[r0] into r4; +assert.eq r3 r4.admin; +cast r1 r0 into r5 as TokenOwner; +hash.bhp256 r5 into r6 as field; +set r2 into roles[r6]; + +function remove_role: +input r0 as field.public; +input r1 as address.public; +is.neq r0 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r2; +assert.eq r2 true; +async remove_role r0 r1 self.caller into r3; +output r3 as multi_token_support_program_v1.aleo/remove_role.future; + +finalize remove_role: +input r0 as field.public; +input r1 as address.public; +input r2 as address.public; +get registered_tokens[r0] into r3; +assert.eq r2 r3.admin; +cast r1 r0 into r4 as TokenOwner; +hash.bhp256 r4 into r5 as field; +remove roles[r5]; + +function mint_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +input r3 as u32.public; +is.neq r0 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r4; +assert.eq r4 true; +async mint_public r0 r1 r2 r3 self.caller into r5; +output r5 as multi_token_support_program_v1.aleo/mint_public.future; + +finalize mint_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +input r3 as u32.public; +input r4 as address.public; +get registered_tokens[r0] into r5; +is.eq r4 r5.admin into r6; +not r6 into r7; +branch.eq r7 false to end_then_0_6; +cast r4 r0 into r8 as TokenOwner; +hash.bhp256 r8 into r9 as field; +get roles[r9] into r10; +is.eq r10 1u8 into r11; +is.eq r10 3u8 into r12; +or r11 r12 into r13; +assert.eq r13 true; +branch.eq true true to end_otherwise_0_7; +position end_then_0_6; +position end_otherwise_0_7; +add r5.supply r2 into r14; +lte r14 r5.max_supply into r15; +assert.eq r15 true; +cast r1 r0 into r16 as TokenOwner; +hash.bhp256 r16 into r17 as field; +cast r0 r1 0u128 r3 into r18 as Balance; +get.or_use balances[r17] r18 into r19; +get.or_use authorized_balances[r17] r18 into r20; +ternary r5.external_authorization_required r19.token_id r20.token_id into r21; +ternary r5.external_authorization_required r19.account r20.account into r22; +ternary r5.external_authorization_required r19.balance r20.balance into r23; +ternary r5.external_authorization_required r19.authorized_until r20.authorized_until into r24; +cast r21 r22 r23 r24 into r25 as Balance; +add r25.balance r2 into r26; +cast r0 r1 r26 r25.authorized_until into r27 as Balance; +branch.eq r5.external_authorization_required false to end_then_0_8; +set r27 into balances[r17]; +branch.eq true true to end_otherwise_0_9; +position end_then_0_8; +set r27 into authorized_balances[r17]; +position end_otherwise_0_9; +cast r0 r5.name r5.symbol r5.decimals r14 r5.max_supply r5.admin r5.external_authorization_required r5.external_authorization_party into r28 as TokenMetadata; +set r28 into registered_tokens[r0]; + +function mint_private: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +input r3 as boolean.public; +input r4 as u32.public; +is.neq r0 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r5; +assert.eq r5 true; +cast r1 r2 r0 r3 r4 into r6 as Token.record; +async mint_private r0 r2 r3 r4 self.caller into r7; +output r6 as Token.record; +output r7 as multi_token_support_program_v1.aleo/mint_private.future; + +finalize mint_private: +input r0 as field.public; +input r1 as u128.public; +input r2 as boolean.public; +input r3 as u32.public; +input r4 as address.public; +get registered_tokens[r0] into r5; +is.eq r4 r5.admin into r6; +not r6 into r7; +branch.eq r7 false to end_then_0_10; +cast r4 r0 into r8 as TokenOwner; +hash.bhp256 r8 into r9 as field; +get roles[r9] into r10; +is.eq r10 1u8 into r11; +is.eq r10 3u8 into r12; +or r11 r12 into r13; +assert.eq r13 true; +branch.eq true true to end_otherwise_0_11; +position end_then_0_10; +position end_otherwise_0_11; +add r5.supply r1 into r14; +lte r14 r5.max_supply into r15; +assert.eq r15 true; +assert.eq r5.external_authorization_required r2; +is.eq r3 0u32 into r16; +not r5.external_authorization_required into r17; +or r16 r17 into r18; +assert.eq r18 true; +cast r0 r5.name r5.symbol r5.decimals r14 r5.max_supply r5.admin r5.external_authorization_required r5.external_authorization_party into r19 as TokenMetadata; +set r19 into registered_tokens[r0]; + +function burn_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +is.neq r0 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r3; +assert.eq r3 true; +cast r1 r0 into r4 as TokenOwner; +async burn_public r4 r2 self.caller into r5; +output r5 as multi_token_support_program_v1.aleo/burn_public.future; + +finalize burn_public: +input r0 as TokenOwner.public; +input r1 as u128.public; +input r2 as address.public; +get registered_tokens[r0.token_id] into r3; +is.neq r2 r3.admin into r4; +branch.eq r4 false to end_then_0_12; +cast r2 r0.token_id into r5 as TokenOwner; +hash.bhp256 r5 into r6 as field; +get roles[r6] into r7; +is.eq r7 2u8 into r8; +is.eq r7 3u8 into r9; +or r8 r9 into r10; +assert.eq r10 true; +branch.eq true true to end_otherwise_0_13; +position end_then_0_12; +position end_otherwise_0_13; +sub r3.supply r1 into r11; +cast r3.token_id r3.name r3.symbol r3.decimals r11 r3.max_supply r3.admin r3.external_authorization_required r3.external_authorization_party into r12 as TokenMetadata; +set r12 into registered_tokens[r0.token_id]; +cast r0.token_id r0.account 0u128 0u32 into r13 as Balance; +hash.bhp256 r0 into r14 as field; +get.or_use authorized_balances[r14] r13 into r15; +gt r15.balance 0u128 into r16; +branch.eq r16 false to end_then_0_14; +gt r15.balance r1 into r17; +branch.eq r17 false to end_then_1_16; +sub r15.balance r1 into r18; +cast r0.token_id r0.account r18 r15.authorized_until into r19 as Balance; +set r19 into authorized_balances[r14]; +branch.eq true true to end_otherwise_1_17; +position end_then_1_16; +remove authorized_balances[r14]; +sub r1 r15.balance into r20; +get balances[r14] into r21; +sub r21.balance r20 into r22; +cast r0.token_id r0.account r22 r21.authorized_until into r23 as Balance; +set r23 into balances[r14]; +position end_otherwise_1_17; +branch.eq true true to end_otherwise_0_15; +position end_then_0_14; +get balances[r14] into r24; +sub r24.balance r1 into r25; +cast r0.token_id r0.account r25 r24.authorized_until into r26 as Balance; +set r26 into balances[r14]; +position end_otherwise_0_15; + +function burn_private: +input r0 as Token.record; +input r1 as u128.public; +is.neq r0.token_id 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r2; +assert.eq r2 true; +sub r0.amount r1 into r3; +cast r0.owner r3 r0.token_id r0.external_authorization_required r0.authorized_until into r4 as Token.record; +async burn_private r0.token_id r1 self.caller into r5; +output r4 as Token.record; +output r5 as multi_token_support_program_v1.aleo/burn_private.future; + +finalize burn_private: +input r0 as field.public; +input r1 as u128.public; +input r2 as address.public; +get registered_tokens[r0] into r3; +is.eq r2 r3.admin into r4; +not r4 into r5; +branch.eq r5 false to end_then_0_18; +cast r2 r0 into r6 as TokenOwner; +hash.bhp256 r6 into r7 as field; +get roles[r7] into r8; +is.eq r8 2u8 into r9; +is.eq r8 3u8 into r10; +or r9 r10 into r11; +assert.eq r11 true; +branch.eq true true to end_otherwise_0_19; +position end_then_0_18; +position end_otherwise_0_19; +sub r3.supply r1 into r12; +cast r0 r3.name r3.symbol r3.decimals r12 r3.max_supply r3.admin r3.external_authorization_required r3.external_authorization_party into r13 as TokenMetadata; +set r13 into registered_tokens[r0]; + +function prehook_public: +input r0 as TokenOwner.public; +input r1 as u128.public; +input r2 as u32.public; +async prehook_public r0 r1 r2 self.caller into r3; +output r3 as multi_token_support_program_v1.aleo/prehook_public.future; + +finalize prehook_public: +input r0 as TokenOwner.public; +input r1 as u128.public; +input r2 as u32.public; +input r3 as address.public; +get registered_tokens[r0.token_id] into r4; +is.eq r3 r4.external_authorization_party into r5; +assert.eq r5 true; +cast r0.token_id r0.account 0u128 0u32 into r6 as Balance; +hash.bhp256 r0 into r7 as field; +get.or_use balances[r7] r6 into r8; +get.or_use authorized_balances[r7] r6 into r9; +lt r9.authorized_until block.height into r10; +add r8.balance r9.balance into r11; +ternary r10 r11 r8.balance into r12; +ternary r10 0u128 r9.balance into r13; +sub r12 r1 into r14; +add r13 r1 into r15; +cast r0.token_id r0.account r15 r2 into r16 as Balance; +set r16 into authorized_balances[r7]; +cast r0.token_id r0.account r14 r8.authorized_until into r17 as Balance; +set r17 into balances[r7]; + +function prehook_private: +input r0 as Token.record; +input r1 as u128.private; +input r2 as u32.private; +sub r0.amount r1 into r3; +cast r0.owner r3 r0.token_id r0.external_authorization_required r0.authorized_until into r4 as Token.record; +cast r0.owner r1 r0.token_id r0.external_authorization_required r2 into r5 as Token.record; +async prehook_private r0.token_id self.caller into r6; +output r4 as Token.record; +output r5 as Token.record; +output r6 as multi_token_support_program_v1.aleo/prehook_private.future; + +finalize prehook_private: +input r0 as field.public; +input r1 as address.public; +get registered_tokens[r0] into r2; +is.eq r1 r2.external_authorization_party into r3; +assert.eq r3 true; + +function approve_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +async approve_public r0 r1 r2 self.caller into r3; +output r3 as multi_token_support_program_v1.aleo/approve_public.future; + +finalize approve_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +input r3 as address.public; +cast r3 r0 into r4 as TokenOwner; +cast r3 r1 r0 into r5 as Allowance; +hash.bhp256 r5 into r6 as field; +get.or_use allowances[r6] 0u128 into r7; +add r7 r2 into r8; +set r8 into allowances[r6]; + +function unapprove_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +async unapprove_public r0 r1 r2 self.caller into r3; +output r3 as multi_token_support_program_v1.aleo/unapprove_public.future; + +finalize unapprove_public: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +input r3 as address.public; +cast r3 r1 r0 into r4 as Allowance; +hash.bhp256 r4 into r5 as field; +get allowances[r5] into r6; +sub r6 r2 into r7; +set r7 into allowances[r5]; + +function transfer_from_public: +input r0 as field.public; +input r1 as address.public; +input r2 as address.public; +input r3 as u128.public; +async transfer_from_public r0 r1 r2 r3 self.caller into r4; +output r4 as multi_token_support_program_v1.aleo/transfer_from_public.future; + +finalize transfer_from_public: +input r0 as field.public; +input r1 as address.public; +input r2 as address.public; +input r3 as u128.public; +input r4 as address.public; +cast r1 r4 r0 into r5 as Allowance; +hash.bhp256 r5 into r6 as field; +get allowances[r6] into r7; +sub r7 r3 into r8; +set r8 into allowances[r6]; +cast r1 r0 into r9 as TokenOwner; +hash.bhp256 r9 into r10 as field; +get authorized_balances[r10] into r11; +get registered_tokens[r0] into r12; +lte block.height r11.authorized_until into r13; +not r12.external_authorization_required into r14; +or r13 r14 into r15; +assert.eq r15 true; +sub r11.balance r3 into r16; +cast r0 r1 r16 r11.authorized_until into r17 as Balance; +set r17 into authorized_balances[r10]; +cast r2 r0 into r18 as TokenOwner; +hash.bhp256 r18 into r19 as field; +get registered_tokens[r0] into r20; +ternary r20.external_authorization_required 0u32 4294967295u32 into r21; +cast r0 r2 0u128 r21 into r22 as Balance; +get.or_use balances[r19] r22 into r23; +get.or_use authorized_balances[r19] r22 into r24; +ternary r20.external_authorization_required r23.token_id r24.token_id into r25; +ternary r20.external_authorization_required r23.account r24.account into r26; +ternary r20.external_authorization_required r23.balance r24.balance into r27; +ternary r20.external_authorization_required r23.authorized_until r24.authorized_until into r28; +cast r25 r26 r27 r28 into r29 as Balance; +add r29.balance r3 into r30; +cast r0 r2 r30 r29.authorized_until into r31 as Balance; +branch.eq r20.external_authorization_required false to end_then_0_20; +set r31 into balances[r19]; +branch.eq true true to end_otherwise_0_21; +position end_then_0_20; +set r31 into authorized_balances[r19]; +position end_otherwise_0_21; + +function transfer_from_public_to_private: +input r0 as field.public; +input r1 as address.public; +input r2 as address.private; +input r3 as u128.public; +input r4 as boolean.public; +ternary r4 0u32 4294967295u32 into r5; +cast r2 r3 r0 r4 r5 into r6 as Token.record; +async transfer_from_public_to_private r0 r1 r3 self.caller r4 into r7; +output r6 as Token.record; +output r7 as multi_token_support_program_v1.aleo/transfer_from_public_to_private.future; + +finalize transfer_from_public_to_private: +input r0 as field.public; +input r1 as address.public; +input r2 as u128.public; +input r3 as address.public; +input r4 as boolean.public; +get registered_tokens[r0] into r5; +assert.eq r5.external_authorization_required r4; +cast r1 r3 r0 into r6 as Allowance; +hash.bhp256 r6 into r7 as field; +get allowances[r7] into r8; +sub r8 r2 into r9; +set r9 into allowances[r7]; +cast r1 r0 into r10 as TokenOwner; +hash.bhp256 r10 into r11 as field; +get authorized_balances[r11] into r12; +get registered_tokens[r0] into r13; +lte block.height r12.authorized_until into r14; +not r13.external_authorization_required into r15; +or r14 r15 into r16; +assert.eq r16 true; +sub r12.balance r2 into r17; +cast r0 r1 r17 r12.authorized_until into r18 as Balance; +set r18 into authorized_balances[r11]; + +function deposit_credits_public: +input r0 as u64.public; +call credits.aleo/transfer_public_as_signer multi_token_support_program_v1.aleo r0 into r1; +cast r0 into r2 as u128; +async deposit_credits_public r1 r2 self.signer into r3; +output r3 as multi_token_support_program_v1.aleo/deposit_credits_public.future; + +finalize deposit_credits_public: +input r0 as credits.aleo/transfer_public_as_signer.future; +input r1 as u128.public; +input r2 as address.public; +await r0; +cast r2 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r3 as TokenOwner; +hash.bhp256 r3 into r4 as field; +cast 3443843282313283355522573239085696902919850365217539366784739393210722344986field r2 0u128 4294967295u32 into r5 as Balance; +get.or_use authorized_balances[r4] r5 into r6; +add r6.balance r1 into r7; +cast 3443843282313283355522573239085696902919850365217539366784739393210722344986field r2 r7 r6.authorized_until into r8 as Balance; +set r8 into authorized_balances[r4]; + +function deposit_credits_private: +input r0 as credits.aleo/credits.record; +input r1 as u64.private; +call credits.aleo/transfer_private_to_public r0 multi_token_support_program_v1.aleo r1 into r2 r3; +cast r1 into r4 as u128; +cast r0.owner r4 3443843282313283355522573239085696902919850365217539366784739393210722344986field false 4294967295u32 into r5 as Token.record; +async deposit_credits_private r3 into r6; +output r2 as credits.aleo/credits.record; +output r5 as Token.record; +output r6 as multi_token_support_program_v1.aleo/deposit_credits_private.future; + +finalize deposit_credits_private: +input r0 as credits.aleo/transfer_private_to_public.future; +await r0; + +function withdraw_credits_public: +input r0 as u64.private; +call credits.aleo/transfer_public self.caller r0 into r1; +cast r0 into r2 as u128; +async withdraw_credits_public r1 r2 self.caller into r3; +output r3 as multi_token_support_program_v1.aleo/withdraw_credits_public.future; + +finalize withdraw_credits_public: +input r0 as credits.aleo/transfer_public.future; +input r1 as u128.public; +input r2 as address.public; +await r0; +cast r2 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r3 as TokenOwner; +hash.bhp256 r3 into r4 as field; +get authorized_balances[r4] into r5; +sub r5.balance r1 into r6; +cast 3443843282313283355522573239085696902919850365217539366784739393210722344986field r2 r6 r5.authorized_until into r7 as Balance; +set r7 into authorized_balances[r4]; + +function withdraw_credits_private: +input r0 as Token.record; +input r1 as u64.private; +is.eq r0.token_id 3443843282313283355522573239085696902919850365217539366784739393210722344986field into r2; +assert.eq r2 true; +call credits.aleo/transfer_public_to_private r0.owner r1 into r3 r4; +cast r1 into r5 as u128; +sub r0.amount r5 into r6; +cast r0.owner r6 r0.token_id r0.external_authorization_required r0.authorized_until into r7 as Token.record; +async withdraw_credits_private r4 into r8; +output r7 as Token.record; +output r3 as credits.aleo/credits.record; +output r8 as multi_token_support_program_v1.aleo/withdraw_credits_private.future; + +finalize withdraw_credits_private: +input r0 as credits.aleo/transfer_public_to_private.future; +await r0; +``` diff --git a/arc-0030/README.md b/arc-0030/README.md index 957f109..6ab95d2 100644 --- a/arc-0030/README.md +++ b/arc-0030/README.md @@ -4,7 +4,7 @@ title: Adding self.parent opcode authors: evan@demoxlabs.xyz mike@demoxlabs.xyz discussion: https://github.com/AleoHQ/ARCs/discussions/11 topic: Application -status: Draft +status: Deprecated created: 9/2/2022 --- diff --git a/arc-0042/README.md b/arc-0042/README.md new file mode 100644 index 0000000..d149e92 --- /dev/null +++ b/arc-0042/README.md @@ -0,0 +1,86 @@ +--- +arc: 42 +title: Adjust block reward algorithm to make sure inflation is stable +authors: Haruka Ma +discussion: https://github.com/ProvableHQ/ARCs/discussions/76 +topic: Protocol +status: Final +created: 2024-09-09 +--- + +## Update + +This ARC has been refined, implemented, and incorporated into snarkVM [here](https://github.com/ProvableHQ/snarkVM/pull/2569). + +## Abstract + +The AleoBFT consensus doesn't guarantee a stable block interval, but the current block reward algorithm assumes the block interval is fixed at 10 seconds, and is emitting rewards regardless of actual block intervals. This makes the 5% annual reward target almost unattainable: shorter block intervals causes higher reward frequency and higher annual reward, and vice versa. + +As an example, if the block interval is 3 seconds throughout a full year, the effective inflation rate from base block reward would be 16.67% instead of 5%. + +This ARC proposes changes to the block reward algorithm so it could adjust the per-block reward according to the actual block interval, therefore achieving the 5% annual target without being affected by fluctuations of the validator network. + + +## Specification + +Proposed new block reward algorithm and implementation: + +```rs +/// Calculate the block reward, given the total supply, block interval, coinbase reward, and transaction fees. +/// R_staking = floor((0.05 * S) * min(I, 60) / S_Y) + CR / 3 + TX_F. +/// S = Total supply. +/// I = Seconds since last block. +/// S_Y = Seconds in a year (31536000). +/// CR = Coinbase reward. +/// TX_F = Transaction fees. +pub fn block_reward( + total_supply: u64, + secs_since_last_block: i64, + coinbase_reward: u64, + transaction_fees: u64, +) -> u64 { + // Compute the annual reward: (0.05 * S). + let annual_reward = total_supply / 20; + // Compute the block reward: (0.05 * S) * min(I, 60) / S_Y. + let block_reward = annual_reward * secs_since_last_block.min(60) as u64 / 31536000; + // Return the sum of the block reward, coinbase reward, and transaction fees. + block_reward + (coinbase_reward / 3) + transaction_fees +} +``` + +The proposed algorithm takes the actual block interval into account, emitting less reward if block interval is short, and vice versa. + +Note the block interval is capped to 60 seconds in the calculation; this is set mainly to remedy situations like chain halts or the gap between genesis timestamp and first block (only applicable to future chain resets for testnets) so there won't be a sudden large sum of credits being rewarded in one block. The 60 seconds here is just an arbitrary number; actual interval cap should be discussed further. + +Also note the block interval is guaranteed to be greater than zero on the consensus level. + +### Test Cases + +Tested with the above code on local devnet: +![image](https://github.com/user-attachments/assets/6b0ffd7e-9d89-4de3-bf4a-b091e4c3f4ee) + + +## Dependencies + +snarkVM. Potentially other products that relies on the implementation. + +### Backwards Compatibility + +Implementing this ARC requires the snarkVM and snarkOS to have a proper soft fork mechanism. + + +## Security & Compliance + +The proposed algorithm affects the tokenomics, albeit it's trying to bring the chain back to the intended state. + +### Conflict of interests among validators + +This section lists some situations the author could think of. + +1. As the block reward linearly increases with the block interval, validators might want to hold the block generation until the interval cap is reached. The only incentive here could be to save some CPU cycles related to block generation, as in the end the total block rewards will be the same regardless of the blocks generated in a given period. Plus, holding back the blocks might cause additional puzzle solutions to abort, as there could be at most 4 solutions in a block, and considering puzzle rewards contributes to the block reward as well, doing this could actually harm the income of validators. +2. Validators might collude to generate blocks with fake timestamps in order to inflate the rewards. In short-term, this might give validators more rewards. However, by being dishonest, validators will be harming the credibility of the chain; therefore in the long run, this would harm the investment done by validators considering the entry barrier of becoming one. Setting the interval cap to the previously intended block interval (10 seconds) might deter this as well. + + +## References + +N/A diff --git a/arc-0100/README.md b/arc-0100/README.md new file mode 100644 index 0000000..8e2c926 --- /dev/null +++ b/arc-0100/README.md @@ -0,0 +1,68 @@ +--- +arc: 100 +title: Compliance Best Practices for Developers and Operators on Aleo +authors: apruden2008 +discussion: https://github.com/ProvableHQ/ARCs/discussions/74 +topic: Meta +status: Living +created: 2024-16-06 +--- + +### Introduction ### + +The Aleo Network Foundation (“ANF”) as well as the broader community of users and developers has a vested interest in minimizing illicit finance and other extralegal transactions from taking place on the Aleo network. To that end, we set forth the following best practices for bridges and validators to help minimize risks and create a robust network for our infrastructure service providers, developers, and end users. The Aleo Network Foundation has consulted with experts in the field, industry groups, and our community to inform this proposal. + +The below are best practices for bridges and validators because they are the key gating mechanisms. We propose a set of criteria to require (i) a time lock on funds and assets brought onto the Aleo network, (ii) blacklisting of suspect bridges, actors, and accounts on Aleo, (iii) robust cybersecurity measures to block bad actors, and (iv) monitoring and enforcement mechanisms. While we will require these best practices of any grantee of ANF, we cannot implement these unilaterally as a provider of a decentralized network. However, we will employ incentives, ecosystem best practices, and promote tools like blacklists to minimize illicit finance and other illegal transactions on the Aleo network. + +### Best Practices for Bridges on Aleo ### + +In the beginning, liquidity on Aleo will come from two sources: centralized exchanges and bridging protocols. We assume as part of this document that centralized exchanges are required to follow the compliance requirements in their respective jurisdictions. Therefore, the recommendations outlined below are designed to be implemented by decentralized *bridge* operators on the Aleo Network. + +The best practices for bridge operators on Aleo are: + +* Implement a 24-72 hour delay at the point of bridging, to be determined dynamically based on the security environment. +* Set a limit of $10,000 USD/day in equivalent value per address as the starting de minimis amount to mirror the Currency Transaction Report guidelines. Bridges should revisit this $10,000 threshold on a regular basis to reassess if this limit should be higher or lower based on information from enforcement agencies and blockchain forensics firms. As part of the best practices, bridges should also consider delaying lower amounts during extreme situations that require the 72-hour holds. +* Bridges could adopt identity tools like zPass, KYC protocols, etc. to shorten or bypass the lockup periods. +* During this timelock, bridges should screen against illicit funds and malicious actors, such as the following (non-exhaustive): + * sanctioned addresses listed on OFAC + * addresses suspected of being in the process of performing DeFi related hacks + * addresses that have interacted with OFAC-sanctioned addresses + * addresses involved in previous DeFi related hacks + * addresses that have received funds from sanctioned wallets +* Guard against transactions from prohibited jurisdictions. Here, we define ‘prohibited jurisdictions’ to include Comprehensively Sanctioned Jurisdictions, as designated by OFAC. However, operators should determine their reasonable risk thresholds for your landscape vis-à-vis their business model and operating landscape. Depending on their landscape, operators are strongly encouraged to include High Risk Jurisdictions by referencing a variety of factors to determine the risk. +* Adopt, at a minimum, standard cybersecurity practices: + * geofencing to block IP addresses from Comprehensively Sanctioned Jurisdictions at the frontend (website or wallet to access a bridge) + * blocking wallet addresses that connect with IP addresses from those jurisdictions. +* As part of this best practice, we recommend a two-strikes policy or an appeals process to account for false positive VPN connections, transitory IP addresses, travel pattern discrepancies (e.g., EU citizens traveling to Cuba legally, but flagged by US systems) and other technical errors. + * blocking known VPN IP addresses indexed to high risk proxies +* Implement real-time monitoring to ensure they are tracking sanctioned and stolen funds lists, from third-party services that monitor on-chain and off-chain data. +* bridges should consider an appeals mechanism in case of technical errors + +### Enforcement mechanisms ### + +Suggested enforcement mechanisms for the above best practices for bridges: +* We envision a multi-pronged enforcement mechanism: + * Protocol-level ability to freeze bridges: Validators will have the ability to freeze blacklisted bridges (and programs generally) as part of the protocol (see the section below titled "References/Further Reading") + * Ecosystem monitoring: We anticipate a custom-built solution that will allow bridges to incorporate real-time data on hacks or other security breaches that could significantly limit the usefulness of the network to illicit actors. We view this as a potentially highly valuable service. The Aleo Network Foundation will also help provide financial support on a case-by-case basis to bridge operators for a limited time-window post-launch. + * Incentives for bridges that implement best practices: We expect bridges to bear the legal and reputational liability if the bridge or application fails to follow these best practices. However, through the leverage of our grants program and our plan to continually identify bad practices and develop further best practices, we expect to continue to provide support for enforcement activities. + +### Proposed Best Practices for Aleo Validators ### + +Validators perform the essential role of operating the network by processing transactions, creating new blocks, and holding one another accountable for following the protocol; all in a decentralized way. These + +* (Primary) Adopt a blacklist for bridges (community derived, OFAC, and other sources). Validators could also adopt Chainabuse (or similar) as an additional feed for exploit addresses. See bullet 1 of "Enforcement Mechanisms" above. + * Every validator will maintain a blacklist of programs for which it will not process or propagate transactions. This list will be initialized to `null` at Genesis + * To prevent a potential network fork, this blacklist should be subject to governance, and ideally consensus should be reached among all stakeholders before any changes to the blacklist are adopted +* Guard against transactions from prohibited jurisdictions. Here, we define ‘prohibited jurisdictions’ to include Comprehensively Sanctioned Jurisdictions, as designated by OFAC. However, operators should determine their reasonable risk thresholds for your landscape vis-à-vis your business model and operating landscape. Depending on their landscape, operators may want to reference High Risk Jurisdictions by referencing a variety of factors to determine the risk. +* Implement, at a minimum, standard cybersecurity practices: + * geofencing to block IP addresses from Comprehensively Sanctioned Jurisdictions at the frontend (website or wallet to access a bridge) + * blocking wallet addresses that connect with IP addresses from those jurisdictions. + * As part of this best practice, we recommend a two-strikes policy or an appeals process to account for false positive VPN connections, transitory IP addresses, travel pattern discrepancies (e.g., EU citizens traveling to Cuba legally, but flagged by US systems) and other technical errors. + * blocking known VPN IP addresses indexed to high risk proxies + * implement real-time monitoring to ensure they are tracking sanctioned and stolen funds lists, from third-party services that monitor on-chain and off-chain data. + * consider an appeals mechanism in case of technical errors + +### References / Further Reading ### + +* `snarkVM` [PR](https://github.com/AleoNet/snarkVM/pull/2487) implementing the program blacklist +* `snarkOS` [PR](https://github.com/AleoNet/snarkOS/pull/3306) implementing the program blacklist diff --git a/arc-0101/README.md b/arc-0101/README.md new file mode 100644 index 0000000..262a2bf --- /dev/null +++ b/arc-0101/README.md @@ -0,0 +1,66 @@ +--- +arc: 101 +title: Requirements and Evaluation Criteria for Validators +authors: zack_xb +discussion: https://github.com/ProvableHQ/ARCs/discussions/82 +topic: Meta +status: Living +created: 2024-12-13 +--- + +## Introduction + +As the Aleo network grows and matures, ensuring a robust, secure, and equitable validator ecosystem is paramount. +This ARC sets forth a proposed standard for validators participating in the Aleo Network, detailing guidelines and evaluation criteria that strive to maintain network reliability, resilience, and fairness. +These guidelines have been developed in close collaboration between the Aleo Network Foundation (ANF) and the Aleo community, drawing on insights gained from ongoing network operations and community feedback. +The purpose of this ARC is to establish transparent, measurable requirements and encourage a merit-based validator selection and evaluation process. +By adhering to these guidelines, validators can help safeguard the network’s long-term stability and align with Aleo’s mission of fostering a decentralised and secure ecosystem. + +## Validator Requirements Guideline + +1. Performance Requirements + - Network Uptime: Validators must maintain a high level of availability. Extended downtime should not exceed 12 hours. + - Node Maintenance: Validators must maintain Canary, Testnet and Mainnet validator nodes and 3 client nodes. + - Monitoring Metrics: Validators must regularly submit both Testnet and Mainnet validator metrics for ongoing assessment of performance and reliability. + - Consensus Liveliness & Recovery: Validators must actively participate in consensus and promptly recover if disconnected, ensuring the continuity and correctness of the ledger. + - Client Synchronization: Validators must successfully complete required synchronization tests following network upgrades to confirm the network’s operational readiness within 24 hours of announcement. + +2. Security Requirements + - Penetration Testing & Security Audits: Validators must conduct and furnish results of regular penetration tests and security audits, at a minimum on an annual basis or as requested by the ANF. + - Failover & Infrastructure Resilience: Validators must maintain robust security measures, including backup nodes, monitoring and alerting systems, firewalls, and secure key management solutions to mitigate risks and safeguard network integrity. + +3. Tokenomics Requirements + - Stake Limitations: + - Validators must not exceed 25% of the total network stake. + - Validators must maintain a minimum stake of 10,000,000 Aleo Credits (or the current minimum stake) to remain active. + +4. Hardware Requirements + - Minimum Specifications: Validators must meet or exceed hardware parameters set by the ANF, including: + - CPU: 64 cores (128 cores or more preferred) + - RAM: 256GiB (384GiB or more preferred) + - Storage: 4TB (6TB or more preferred) + - Network: 500Mbps minimum upload/download bandwidth + +5. Participation Requirements + - Maintenance & Support: Validators must respond to requests for upgrades within 24 hours and complete such upgrades within the specified timeframe. They must also remain responsive (within 24 hours) for urgent network issues and coordination events. + - Node Operations: Participation in Canary, Testnet, Mainnet node and Mainnet Client node operations is required to ensure ongoing network quality. + - Governance & Community Engagement: Validators must participate in at least 80% of governance votes and regularly attend coordination calls. Missing more than 15% of the calls in any quarter without prior notice is considered a violation of these guidelines. + +## Validator Evaluation Criteria for Foundation Delegation + +The ANF will periodically evaluate validators based on the following criteria. Each category carries weight and contributes to the overall assessment of a validator’s contributions and reliability: + +1. Validator operation requirements (as outlined above) +2. Technical Contribution Open-source code contributions to Aleo network. Development or maintenance of validator tools. (e.g., Dashboards, Documentation, Guides). Operation of reliable RPC endpoints and similar infrastructure services. +3. Ecosystem Contribution Development and deployment of dApps on the Aleo ecosystem. Creation and distribution of educational content, tutorials, and community materials. Hosting or contributing to events (workshops, meetups) that grow the community and developer base. +4. Participation & Communication Timely and clear communication with the ANF and community. Participation in governance discussions and responsiveness to critical network updates. +5. Strategic Importance Geographic diversity to ensure a globally distributed and resilient validator set. Engagement with local ecosystems, key market players, and community builders. Business development efforts that foster ecosystem partnerships and network growth. +6. Long-Term Collaborations (Vendors, Partners, Grantees) Participation in events and marketing initiatives that promote the Aleo network. Demonstrated long-term commitment to ecosystem development and improvement. +7. Governance Active voting behaviour and thoughtful input on governance proposals. Constructive participation in community decision-making processes. + +## Conclusion + +By setting forth these guidelines and evaluation criteria, we aim to strengthen the Aleo validator community, ensuring that participants meet the highest standards of performance, security, fairness, and ecosystem engagement. +This ARC serves as a foundational reference for validators, the ANF, and the community, facilitating transparent evaluation and ongoing improvement of the Aleo network. +We invite feedback and comments on this ARC from the Aleo community. Your insights, questions, and suggestions will help refine these guidelines and ensure they serve the best interests of the network and its participants. +Together, we will continue to build a secure, inclusive, and sustainable Aleo ecosystem. diff --git a/arc-0721/README.md b/arc-0721/README.md new file mode 100644 index 0000000..3eea1be --- /dev/null +++ b/arc-0721/README.md @@ -0,0 +1,399 @@ +--- +arc: 721 +title: Aleo Non-Fungible Token Standard +authors: Pierre-André LONG - pa@zsociety.io +discussion: https://github.com/ProvableHQ/ARCs/discussions/79 +topic: Application +status: Accepted +created: 2024-10-14 +--- + +## Abstract + +Several NFT standards have already been proposed on Aleo. This proposal aims at reconciling these approaches to allow the broadest range of use cases made possible by Aleo’s unique privacy features. + +Compared to NFTs on public ledgers like Ethereum, Aleo NFTs can independently have: + +- Private or public token owner visibility. +- Private or public data associated the token. + +Both of those features have implications on feasibility of building applications involving NFTs as a marketplaces, escrow programs… This proposal aims to integrate those features on top of the previous standard proposals while keeping those applications possible. + +Example: + +- Domain Names - Human-readable names that resolve addresses in the [Aleo Name Service contract](https://github.com/S-T-Soft/aleo-name-service-contract). +- Royalty NFTs - Tradable assets which utility is to claim creator or marketplace royalty fees. +- IOU NFTs - Tradable assets allowing to claim due fungible tokens in a lending agreement, including private loan data. + +[The complete code for the standard is available here.](https://github.com/zsolutions-io/aleo-standard-programs/blob/main/arc721/src/main.leo) + +## Motivations + +### On-chain vs Off-chain data + +The proposed standard allows for NFT data to be on-chain, off-chain or a combination of both. Off-chain to reduce the storage fees on the network. On-chain to leverage the possibility of using this data as input/outputs of zk-circuits. + +Remark: On-chain data can either consists of a hash of some data or be the data itself directly depending on the use case’s requirement of guarantying access to the data transactionally. + +## Specifications + +A NFT collection is defined as a program implementing the following specifications. + +### Strings + +As NFTs heavily rely on the use of strings, either for URL to off-chain data or for data itself, they require to standardize encoding of strings into Aleo plaintexts: + +```rust +// Leo +string: [field; 4], +``` + +```rust +// Aleo instructions +string as [field; 4u32]; +``` + +Length of the array can be freely adapted to match the maximum amount of characters required by the collection. + +The choice of fields type is motivated by the fact that they offer close to twice the amount of data for the same constraints as u128. An array of u8, while making application more readable, is even less optimal. + +This specification for strings is compatible with ARC21 standard for name and symbol of fungible tokens. + +[Here is a Javascript example of convertions between js string and aleo plaintext.](https://github.com/zsolutions-io/aleo-standard-programs/blob/main/arc721/utils/strings.js) + +### Data Structure + +The data stored within a NFT has the following structure: + +```rust +struct attribute { + trait_type: [field; 4], + _value: [field; 4], +} + +struct data { + metadata: [field; 4], // URI of offchain metadata JSON + // (optional) name: [field; 4], + // (optional) image: [field; 16], + // (optional) attributes: [attribute; 4], + // (optional) ... +} +``` + +An example of such an off-chain metadata JSON can be found [here](https://aleo-public.s3.us-west-2.amazonaws.com/testnet3/privacy-pride/1.json). + +Name of the structs don’t necessarily have to match ‘data’ and a’ttribute’, allowing to import several NFT collection program without shadowing. Although, the name of each struct attribute is enforced by the standard. Aleo reserved keywords should be prefixed with an underscore character (as for ‘_value’). + +To get the complete data for a NFT, off-chain and on-chain data can simply be merged, for instance in javascript: + +```jsx +const nft_data = { + ...await (await fetch(nft.data.metadata)).json(), + ...nft.data, // on-chain data overrides off-chain data +} +``` + +### Private Data and Ownership + +As for ARC20-21 tokens, privacy of NFT owner is achieved by representing the token as an Aleo record. It also allows the data of an NFT to remain private, by including it as a private attribute of the record. + +Although, to enforce uniqueness, a public identifier for the NFT is necessary. Simply using a hash of the data would introduce 2 problems: + +- Two NFTs could not share the same data. +- It would disclose some knowledge about the data: verifying that some data matches would be an instantaneous operation. + +For these reasons, we include in the NFT record a scalar element, called edition: + +```rust +record NFT { + private owner: address, + private data: data, + private edition: scalar, +} +``` + +We define the public identifier for the NFT as the commit of its edition to the hash of its data: + +```rust +inline commit_nft( + nft_data: data, + nft_edition: scalar +) -> field { + let data_hash: field = BHP256::hash_to_field(nft_data); + let nft_commit: field = BHP256::commit_to_field(data_hash, nft_edition); + return nft_commit; +} +``` + +This “NFT commit” does not disclose any information on the NFT data as long as the edition obfuscator is chosen uniformly randomly in the scalar field. + +The following mapping serves to enforce the uniqueness of “NFT commit” identifiers: + +```rust +mapping nft_commits: field => bool; +// NFT commit => NFT exists or has existed +``` + +### Public Ownership + +Apart from guarantying non-fungibility, NFT commit also allows to make the owner of the NFT public, while keeping its data private. + +This is a key feature on Aleo because it allows program to own NFTs without revealing their data, as programs cannot spend records. + +The same as for ARC20-21 tokens, an NFT owner can be made public by mapping NFT commit to said owner: + +```rust +mapping nft_owners: field => address; +// NFT commit => NFT owner +``` + +Although it raises a challenging question, if user A is the private owner of a NFT, and transfers ownership to a public owner user B, how can user B know the actual data behind the public NFT commit. + +The proposed solution is to include another record, `NFTView`, defined as: + +```rust +record NFTView { + private owner: address, + private data: data, + private edition: scalar, + private is_view: bool +} +``` + +This record doesn’t represent private ownership of the NFT, but is a vehicle for the NFT data and is minted to the public receiver of transfers along with NFT ownership. + +The conversion from private to public owner can then be implemented as follow: + +```rust +async transition transfer_private_to_public( + private nft: NFT, + public to: address, +) -> (NFTView, Future) { + let nft_commit: field = commit_nft(nft.data, nft.edition); + let nft_view: NFTView = NFTView { + owner: to, + data: nft.data, + edition: nft.edition, + is_view: true + }; + let transfer_private_to_public_future: Future = + finalize_transfer_private_to_public( + to, nft_commit + ); + return ( + nft_view, + transfer_private_to_public_future + ); +} +async function finalize_transfer_private_to_public( + to: address, + nft_commit: field, +){ + nft_owners.set( + nft_commit, + to + ); +} +``` + +Remark: `is_view` is always true, and is here just for differentiating `NFTView` from `NFT` in plaintext representations of records. + +An ultimate problem remains: what if the public receiver of a transfer is a program? Then NFTView doesn’t help. + +To illustrate this problem, let’s imagine we are trying to create a marketplace program. A seller lists the NFT using `transfer_private_to_public`. A buyer then accepts the listing and should receive automatically receive the data corresponding to the NFT. One way to do this, could be to have the seller come back, to disclose the private data to withdraw payment. + +This back and forth between the buyer and the seller makes the user experience a lot worse than on traditional NFT marketplaces. Hence the need for a mechanism allowing Aleo programs to store private data and disclose it programmatically. An attempt at contributing solving this problem is [Aleo DCP](https://github.com/zsolutions-io/aleo-dcp), where data is splitted according to a MPC protocol. Here is an [example NFT marketplace program](https://github.com/zsolutions-io/aleo-dcp/blob/main/examples/nft_marketplace/programs/marketplace_example/src/main.leo) with the same “one click buy” user experience as with traditional marketplaces, by leveraging Aleo DCP. + +Remark: These last considerations are only relevant to NFTs data that should always remain private. + +### Public Data + +For collections where data can become public, “publishable collections”, the following mapping and function should be included to tackle the problem stated in last section: + +```rust +struct nft_content { + data: data, + edition: scalar +} + +mapping nft_contents: field => nft_content; + +async transition publish_nft_content( + public nft_data: data, + public nft_edition: scalar, +) -> Future { + let nft_commit: field = commit_nft(nft_data, nft_edition); + let publish_nft_content_future: Future = finalize_publish_nft_content( + nft_commit, + nft_data, + nft_edition, + ); + return publish_nft_content_future; +} +async function finalize_publish_nft_content( + nft_commit: field, + nft_data: data, + nft_edition: scalar, +) { + let public_data: nft_content = nft_content { + data: nft_data, + edition: nft_edition + }; + nft_contents.set(nft_commit, public_data); +} +``` + +`publish_nft_content` can then be called along with transfers to a program, for instace in marketplace program on listing. + +### Re-obfuscation + +If a NFT content has been published once, the only way to re-obfuscate it is to transfer it to private again, then use the following function to update the edition, hence the commit of the NFT. + +```rust +async transition update_edition_private( + private nft: NFT, + private new_edition: scalar, +) -> (NFT, Future) { + let out_nft: NFT = NFT { + owner: nft.owner, + data: nft.data, + edition: new_edition, + }; + let nft_commit: field = commit_nft(nft.data, new_edition); + + let update_edition_private_future: Future = finalize_update_edition_private( + nft_commit + ); + return (out_nft, update_edition_private_future); +} +async function finalize_update_edition_private( + nft_commit: field, +) { + assert(nft_commits.contains(nft_commit).not()); + nft_commits.set(nft_commit, true); +} +``` + +Previous commit is not removed from `nft_commits` mapping, as it would reveal the previous commit and the new one represent the same data. + +### Approvals + +As for ARC20 tokens, the standard features an approval mechanism allowing accounts to approve another account to spend their token. It can be a specific asset, or any asset from the collection. + +```rust +struct approval { + approver: address, + spender: address +} + +mapping for_all_approvals: field => bool; +// Approval hash => Is approved + +mapping nft_approvals: field => field; +// NFT commit => Approval hash + +async transition set_for_all_approval( + private spender: address, + public new_value: bool, +) -> Future { + let apvl: approval = approval { + approver: self.caller, + spender: spender, + }; + let apvl_hash: field = BHP256::hash_to_field(apvl); + return finalize_set_for_all_approval( + apvl_hash, new_value + ); +} +async function finalize_set_for_all_approval( + apvl_hash: field, + new_value: bool, +){ + for_all_approvals.set(apvl_hash, new_value); +} + +async transition approve_public( + private spender: address, + private nft_data: data, + private nft_edition: scalar, +) -> Future { + let nft_commit: field = commit_nft(nft_data, nft_edition); + + let apvl: approval = approval { + approver: self.caller, + spender: spender, + }; + let apvl_hash: field = BHP256::hash_to_field(apvl); + return finalize_approve_public( + self.caller, apvl_hash, nft_commit, + ); +} +async function finalize_approve_public( + caller: address, + apvl_hash: field, + nft_commit: field, +){ + let owner: address = nft_owners.get(nft_commit); + assert_eq(owner, caller); + nft_approvals.set(nft_commit, apvl_hash); +} +``` + +Once approved, the `transfer_from_public` function can be called by the spender, to transfer a NFT from the approver to a recipient address. + +### Settings + +A mapping is responsible for the collection level settings: + +```rust +mapping general_settings: u8 => field; +// Setting index => Setting value +``` + +Available settings: + +- **`0u8` -** Amount of mintable NFTs (all editions). +- **`1u8` -** Number of total NFTs (first-editions) that can be minted. +- **`2u8` -** Symbol for the NFT. +- **`3u8` -** Base URI for NFT, part 1. +- **`4u8` -** Base URI for NFT, part 2. +- **`5u8` -** Base URI for NFT, part 3. +- **`6u8` -** Base URI for NFT, part 4. +- **`7u8` -** Admin address hash. + +## Suggested Improvements + +### NFT Registry + +Because current version of SnarkOS/SnarkVM does not support dynamic contract calls, the same approach as for fungible tokens of making a NFT registry program would reduce the amount of programs to deploy on the network. + +It is trickier than for fungible tokens though, because of the requirement to support arbitrary on-chain data structure for NFTs. + +[ARC-0722 proposal discussion can be found here.](https://github.com/AleoNet/ARCs/discussions/80) + +### Settings + +Instead of using a mapping with one index for each setting value, we could use a cleaner metadata struct for the collection, as ARC-20/21 do. + +``` +struct CollectionMetadata { + name: u128, // should this be a field? + symbol: u128, // should this be a field? + supply: u128, + base_uri: [4; field], + max_supply: u128, + admin: address + } +``` + +## Reference Implementations + +Implementation : https://github.com/zsolutions-io/aleo-standard-programs/blob/main/arc721/src/main.leo + +## References + +https://eips.ethereum.org/EIPS/eip-721 + +Especially, this work relies almost entirely on Demox Labs team previous work on Art-Factory: +https://github.com/AleoNet/ARCs/discussions/36 +@fulltimemike, @evanmarshall, @JohnDonavon, @dagarcia7 diff --git a/output/arc-0001_README-md-1.png b/output/arc-0001_README-md-1.png new file mode 100644 index 0000000..57f4a6f Binary files /dev/null and b/output/arc-0001_README-md-1.png differ diff --git a/parser/index.js b/parser/index.js index 5ce6a46..0399634 100644 --- a/parser/index.js +++ b/parser/index.js @@ -15,7 +15,7 @@ const requiredMetadata = ['arc', 'title', 'authors', 'discussion', 'topic', 'sta const topics = ['Protocol', 'Network', 'Application', 'Meta']; -const statuses = ['Draft', 'Active', 'Withdrawn', 'Accepted', 'Rejected', 'Final', 'Deprecated', 'Living']; +const statuses = ['Draft', 'Active', 'Withdrawn', 'Accepted', 'Final', 'Deprecated', 'Living']; // Executes sanity checks for CI. const ci = () => {