Aleo Governance Logo

ARC-20: Fungible Token Standard

METADATA

author: @evanm

status: Discussion

created: May 07 2024 4:58 AM

updated: May 07 2024 3:14 PM

discussion: Discord

Abstract

As a community, we need to settle on an ERC-20 like standard so that wallets, dexes, and other dapps can build interfaces that broadly support all tokens. Many discussions have been had about what exactly should be included in this token standard. Thank you to @r001 for putting together this review of all of the token standards that have been purposed thus far.

This follows the approach put forth by @vicsn in this Aleo ARCs discussion. The primary benefit of this approach is that it makes it clear and simple for programs to build upon and interact with. The main feedback from the discussion was that we need some way to get metadata about the symbol & decimals so that has been updated. The rest of the program follows this PR from the ARC repository

Specification

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;

struct metadata:
    name as u128; // 16 bytes -> 16 characters with ASCII encoding
    symbol as u64; // 8 bytes -> 8 characters with ASCII encoding
    decimals as u8;
    total_supply as u64;

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

function get_metadata:
    cast [name] [symbol] [decimals] [total_supply] into r0 as metadata; // Replace with hardcoded values
    output r0 as metadata.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 approval 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];

Test Cases

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.

Aleo Voting System

Powered by Nance