Skip to content
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

ARC-0080: Explicitly Reject Program Address as Record Owner #57

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions arc-0080/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
---
arc: 0080
title: Explicitly Reject Program Address as Record Owner
authors: randomsleep
discussion: https://github.com/AleoHQ/ARCs/discussions/56
topic: Application
status: Draft
created: 2023-12-29
---

## Abstract

The current Aleo system permits the assignment of a `Record` to any address, including program addresses. However, programs cannot spend a `Record`. Consequently, any `Record` assigned to a program becomes irretrievable, leading to unintended asset loss in Aleo, which can include credits, tokens, NFTs, and more. With the introduction of arc-0030, which enables Aleo users to transfer assets to a program, the frequency of such incidents is likely to increase.

This proposal seeks to explicitly prohibit the creation of a `Record` with a program as the owner, significantly reducing the potential for asset loss.


## Specification

The proposed solution involves modifying the `Record` creation process to reject any attempt to assign a `Record` to a program address. This can be achieved by adding a validation step in the `Record` creation process to check the type of the address. If the address belongs to a program, the process should throw an error and abort the `Record` creation.

For example, if a user tries to create a `Record` with a program address like `aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqquzlzl`, the system should return an error message like "Invalid owner address: A program cannot own a Record."

### Test Cases

The following test cases should be considered:

1. Attempt to create a `Record` with a program address - This should fail with an appropriate error message.
2. Attempt to create a `Record` with a non-program address - This should succeed.
3. Attempt to transfer a `Record` to a program address - This should fail with an appropriate error message.


Considering the following program.

```
import token.leo;

program arc_0080_test.aleo {
transition show_address() -> (address, address) {
let signer: address = self.signer;
let caller: address = token.leo/get_self_address();
return (signer, caller);
}

transition mint_public(public amount: u64) {
token.leo/self_mint_public(amount);
}

transition mint_public_then_transfer(amount: u64) {
let receiver: address = self.signer;
token.leo/self_mint_public(amount);
token.leo/transfer_public(receiver, amount);
}

transition mint_private(amount: u64) -> token.leo/token {
return token.leo/self_mint_private(amount);
}

transition mint_private_then_transfer(amount: u64) -> (token.leo/token, token.leo/token) {
let receiver: address = self.signer;
let sender: token = token.leo/self_mint_private(amount);
return token.leo/transfer_private(sender, receiver, amount);
}
}
```

```
// token.leo
// The `program` scope defines the data types, functions, and state associated with the `token` program.
program token.aleo {
// On-chain storage of an `account` map, with `address` as the key,
// and `u64` as the value.
mapping account: address => u64;

record token {
// The token owner.
owner: address,
// The token amount.
amount: u64,
}

transition get_self_address() -> address {
let caller: address = self.caller;
return caller;
}

/* Mint */
transition self_mint_public(public amount: u64) {
let receiver: address = self.caller;
return then finalize(receiver, amount);
}

finalize self_mint_public(public receiver: address, public amount: u64) {
let receiver_amount: u64 = Mapping::get_or_use(account, receiver, 0u64);
Mapping::set(account, receiver, receiver_amount + amount);
}

transition self_mint_private(amount: u64) -> token {
let receiver: address = self.caller;
return token {
owner: receiver,
amount: amount,
};
}

/* Transfer */
transition transfer_public(public receiver: address, public amount: u64) {
// Transfer the tokens publicly, by invoking the computation on-chain.
return then finalize(self.caller, receiver, amount);
}

finalize transfer_public(public sender: address, public receiver: address, public amount: u64) {
// Decrements `account[sender]` by `amount`.
// If `account[sender]` does not exist, it will be created.
// If `account[sender] - amount` underflows, `transfer_public` is reverted.
let sender_amount: u64 = Mapping::get_or_use(account, sender, 0u64);
Mapping::set(account, sender, sender_amount - amount);

// Increments `account[receiver]` by `amount`.
// If `account[receiver]` does not exist, it will be created.
// If `account[receiver] + amount` overflows, `transfer_public` is reverted.
let receiver_amount: u64 = Mapping::get_or_use(account, receiver, 0u64);
Mapping::set(account, receiver, receiver_amount + amount);
}

// The function `transfer_private` sends the specified token amount to the token receiver from the specified token record.
transition transfer_private(sender: token, receiver: address, amount: u64) -> (token, token) {
// Checks the given token record has sufficient balance.
// This `sub` operation is safe, and the proof will fail if an overflow occurs.
// `difference` holds the change amount to be returned to sender.
let difference: u64 = sender.amount - amount;

// Produce a token record with the change amount for the sender.
let remaining: token = token {
owner: sender.owner,
amount: difference,
};

// Produce a token record for the specified receiver.
let transferred: token = token {
owner: receiver,
amount: amount,
};

// Output the sender's change record and the receiver's record.
return (remaining, transferred);
}
}
```

Run `mint_public` should succeed.

Run `mint_public_then_transfer` should succeed.

Run `mint_private` must fail.

Run `mint_private_then_transfer` must fail.


## Dependencies

This proposal directly affects the Aleo and snarkVM repositories as they contain the logic for `Record` creation and validation.

### Backwards Compatibility

This proposal does not introduce any backwards incompatibility as it adds a new validation step in the `Record` creation process.


## Security & Compliance

This proposal enhances the security of the Aleo platform by preventing unintended asset loss. It does not introduce any new regulatory concerns.