Skip to content

Commit

Permalink
Create new ARC20 spec
Browse files Browse the repository at this point in the history
  • Loading branch information
vicsn committed Nov 1, 2023
1 parent a5a8d8b commit 211dc15
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 0 deletions.
1 change: 1 addition & 0 deletions arc-0020/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build_**
45 changes: 45 additions & 0 deletions arc-0020/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
arc: 20
title: Design for ARC20
authors: The Aleo Team <[email protected]>
discussion:
topic: Application
status: Living
created: 2023-10-31
---

*A big thank you to [Valentin Seehausen](https://github.com/Valentin-Seehausen), [Evan Marshall](https://github.com/evanmarshall), [FullTimeMike](https://github.com/fulltimemike) and authors of the previous two ARC20 specs [Ghostant-1017](https://github.com/ghostant-1017) and [EdVeralli](https://github.com/EdVeralli).*

## Abstract

This ARC introduces a design for minimal Fungible Tokens just like ERC20. It allows for transferring tokens and approving programs to transfer tokens on your behalf.

Given that Aleo does not support interfaces or inheritance, this spec is not enforced by the compiler. However, we invite the community to adhere to these standards in an effort to enhance interoperability between programs.

The previous two ARC20 standards were built on old versions of snarkVM. Now that functionality is stabilizing, we can integrate the community's learnings in a new standard. This minimal initial standard is written in Aleo instructions for simplicity and enhanced auditability. In the future there are many extensions which might be valuable to standardize:
- ERC1155-like multi-token standard
- minting functionality
- multisig or admin functionality

Notes:
- One can approve for more than the existing balance, but spending approved funds from others is of course limited by their balance.
- No metadata is added to the program spec, though deployed program names can suffice as globally unique identifiers.
- An update to snarkVM should soon enable passing an aleo program id as program input to `snarkos developer execute`
- Before committing, this should be audited against the `credits.aleo` program.

## Specification

[token.aleo](./token.aleo)

## Testing

You can test this program on a local devnet. First, set up the devnet. Development private keys and addresses are printed to the terminal. Because the devnet runs in tmux, You can scroll up using `ctrl+b+[`. Be quick because history is limited by default.

```
git clone github.com/aleoHQ/snarkOS
cd snarkOS
git checkout ca3e84c48
./devnet.sh
```

Then run the `test.sh` script from the folder in this ARC repository.
16 changes: 16 additions & 0 deletions arc-0020/spender_tester.aleo
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import token.aleo;

program spender_tester.aleo;

function transfer_from_public:
input r0 as address.public; // approver
input r1 as address.public; // receiver
input r2 as u64.public;

call token.aleo/transfer_from_public r0 r1 r2 into r3;
async transfer_from_public r3 into r4;
output r4 as spender_tester.aleo/transfer_from_public.future;

finalize transfer_from_public:
input r0 as token.aleo/transfer_from_public.future;
await r0;
6 changes: 6 additions & 0 deletions arc-0020/spender_tester_program.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"program": "spender_tester.aleo",
"version": "0.0.0",
"description": "",
"license": "MIT"
}
58 changes: 58 additions & 0 deletions arc-0020/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
set -e

# Read the approver private key from the user
read -p "Enter the private key with positive public account balance: " approver_private_key
# Read the approver addresss from the user
read -p "Enter the associated address with positive public account balance: " approver_address

# throwaway keys
spender_tester_private_key="APrivateKey1zkpB6TurrGgShJ7dsJ21HniMTF5WQc2eRy7d5o5QyBZFMFf"
spender_tester_address="aleo1ew25qvyvd33gk9w4m8ehyhjamsm74r7n3ze9npnpux0rntzajgpqcm26pz"


mkdir -p build_token
cp token.aleo build_token/main.aleo
cp token_program.json build_token/program.json

echo """
/* This is only used for testing the spec */
function mint_public:
input r0 as address.public;
input r1 as u64.public;
async mint_public r0 r1 into r2;
output r2 as token.aleo/mint_public.future;
finalize mint_public:
input r0 as address.public;
input r1 as u64.public;
get.or_use account[r0] 0u64 into r2;
add r2 r1 into r3;
set r3 into account[r0];
""" >> build_token/main.aleo

mkdir -p build_spender_tester
cp spender_tester.aleo build_spender_tester/main.aleo
cp spender_tester_program.json build_spender_tester/program.json
mkdir -p build_spender_tester/imports
cp build_token/main.aleo build_spender_tester/imports/token.aleo

# deploy
snarkos developer deploy token.aleo --private-key ${approver_private_key} --query "http://localhost:3030" --path "build_token" --broadcast "http://localhost:3030/testnet3/transaction/broadcast" --priority-fee 0

snarkos developer deploy spender_tester.aleo --private-key ${approver_private_key} --query "http://localhost:3030" --path "build_spender_tester" --broadcast "http://localhost:3030/testnet3/transaction/broadcast" --priority-fee 0

echo letting deployments settle for a few seconds...
sleep 10

# mint tokens
snarkos developer execute token.aleo mint_public ${approver_address} 10u64 --private-key ${approver_private_key} --query "http://localhost:3030" --broadcast "http://localhost:3030/testnet3/transaction/broadcast"

# Transfer to spender so they have enough to cover the fee
snarkos developer execute credits.aleo transfer_public ${spender_tester_address} 100000u64 --private-key ${approver_private_key} --query "http://localhost:3030" --broadcast "http://localhost:3030/testnet3/transaction/broadcast"

snarkos developer execute token.aleo approve_public ${spender_tester_address} 1u64 --private-key ${approver_private_key} --query "http://localhost:3030" --broadcast "http://localhost:3030/testnet3/transaction/broadcast"

snarkos developer execute token.aleo transfer_from_public ${approver_address} ${spender_tester_address} 1u64 --private-key ${spender_tester_private_key} --query "http://localhost:3030" --broadcast "http://localhost:3030/testnet3/transaction/broadcast"

# TODO: approve big_spender.aleo and make sure they transfer_from_public
148 changes: 148 additions & 0 deletions arc-0020/token.aleo
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
program token.aleo;

mapping account:
key as address.public;
value as u64.public;

record token:
owner as address.private;
amount as u64.private;

struct approval:
approver as address;
spender as address;

mapping approvals:
key as field.public;
value as u64.public;

function approve_public:
input r0 as address.public; // spender
input r1 as u64.public; // amount spender is allowed to withdraw from approver

// hash approval
cast self.caller r0 into r2 as approval;
hash.bhp256 r2 into r3 as field;

async approve_public r3 r1 into r4;
output r4 as token.aleo/approve_public.future;

finalize approve_public:
input r0 as field.public;
input r1 as u64.public; // increase in amount spender is allowed to withdraw from approver

// if approvals for approver field times spender field exists, the approved amount is increased.
// otherwise, the approved allowance is created.
get.or_use approvals[r0] 0u64 into r2;
add r1 r2 into r3;
set r3 into approvals[r0];

function unapprove_public:
input r0 as address.public; // spender
input r1 as u64.public; // amount spender's allowance is decreasing by

// hash approval
cast self.caller r0 into r2 as approval;
hash.bhp256 r2 into r3 as field;

async unapprove_public r3 r1 into r4;
output r4 as token.aleo/unapprove_public.future;

finalize unapprove_public:
input r0 as field.public;
input r1 as u64.public; // decrease in amount spender is allowed to withdraw from approver

get approvals[r0] into r2;
sub r2 r1 into r3;
set r3 into approvals[r0];

/* Transfer From */

function transfer_from_public:
input r0 as address.public; // from the approver
input r1 as address.public; // to the receiver
input r2 as u64.public; // amount to transfer

cast r0 self.caller into r3 as approval;
hash.bhp256 r3 into r4 as field; // hash approval

async transfer_from_public r4 r0 r1 r2 into r5;
output r5 as token.aleo/transfer_from_public.future;

finalize transfer_from_public:
input r0 as field.public; // approval
input r1 as address.public; // from the approver
input r2 as address.public; // to the receiver
input r3 as u64.public; // amount to transfer

get approvals[r0] into r4;
sub r4 r3 into r5;
set r5 into approvals[r0];
get account[r1] into r6;
sub r6 r3 into r7;
set r7 into account[r1];
get.or_use account[r2] 0u64 into r8;
add r8 r3 into r9;
set r9 into account[r2];

function transfer_public:
input r0 as address.public;
input r1 as u64.public;
async transfer_public self.caller r0 r1 into r2;
output r2 as token.aleo/transfer_public.future;

finalize transfer_public:
input r0 as address.public;
input r1 as address.public;
input r2 as u64.public;
get.or_use account[r0] 0u64 into r3;
sub r3 r2 into r4;
set r4 into account[r0];
get.or_use account[r1] 0u64 into r5;
add r5 r2 into r6;
set r6 into account[r1];


function transfer_private:
input r0 as token.record;
input r1 as address.private;
input r2 as u64.private;
sub r0.amount r2 into r3;
cast r0.owner r3 into r4 as token.record;
cast r1 r2 into r5 as token.record;
output r4 as token.record;
output r5 as token.record;


function transfer_private_to_public:
input r0 as token.record;
input r1 as address.public;
input r2 as u64.public;
sub r0.amount r2 into r3;
cast r0.owner r3 into r4 as token.record;
async transfer_private_to_public r1 r2 into r5;
output r4 as token.record;
output r5 as token.aleo/transfer_private_to_public.future;

finalize transfer_private_to_public:
input r0 as address.public;
input r1 as u64.public;
get.or_use account[r0] 0u64 into r2;
add r2 r1 into r3;
set r3 into account[r0];


function transfer_public_to_private:
input r0 as address.public;
input r1 as u64.public;
cast r0 r1 into r2 as token.record;
async transfer_public_to_private self.caller r1 into r3;
output r2 as token.record;
output r3 as token.aleo/transfer_public_to_private.future;

finalize transfer_public_to_private:
input r0 as address.public;
input r1 as u64.public;
get.or_use account[r0] 0u64 into r2;
sub r2 r1 into r3;
set r3 into account[r0];
6 changes: 6 additions & 0 deletions arc-0020/token_program.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"program": "token.aleo",
"version": "0.0.0",
"description": "",
"license": "MIT"
}

0 comments on commit 211dc15

Please sign in to comment.