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

feat: implement erc20 streaming #237

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
32 changes: 21 additions & 11 deletions listings/applications/erc20/src/erc20_streaming.cairo
julio4 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod erc20_streaming {
end_time: u64,
total_amount: felt252,
released_amount: felt252,
to: ContractAddress,
}

#[event]
Expand Down Expand Up @@ -47,6 +48,8 @@ pub mod erc20_streaming {
mod Errors {
pub const STREAM_AMOUNT_ZERO: felt252 = 'Stream amount cannot be zero';
pub const STREAM_ALREADY_EXISTS: felt252 = 'Stream already exists';
pub const END_TIME_INVALID: felt252 = 'End time must be greater than start time';
pub const STREAM_UNAUTHORIZED: felt252 = 'Caller is not the recipient of the stream';
}

#[constructor]
Expand All @@ -60,11 +63,13 @@ pub mod erc20_streaming {
ref self: ContractState,
to: ContractAddress,
total_amount: felt252,
start_time: u64,
end_time: u64
) {
assert(total_amount != felt252::zero(), Errors::STREAM_AMOUNT_ZERO);
let caller = get_caller_address();
let start_time = get_block_timestamp(); // Use block timestamp for start time
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved
assert(end_time > start_time, Errors::END_TIME_INVALID); // Assert end_time > start_time

let stream_key = (caller, to);
assert(self.streams.read(stream_key).start_time == 0, Errors::STREAM_ALREADY_EXISTS);

Expand All @@ -83,13 +88,20 @@ pub mod erc20_streaming {
self.emit(StreamCreated { from: caller, to, total_amount, start_time, end_time });
}

fn release_tokens(ref self: ContractState, to: ContractAddress) {
fn release_tokens(ref self: ContractState, stream_id: u64) {
let caller = get_caller_address();
let stream_key = (caller, to);
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved
let stream = self.streams.read(stream_key);
let stream = self.streams.read(stream_id);
assert(caller == stream.to, Errors::STREAM_UNAUTHORIZED);

let releasable_amount = self.releasable_amount(stream);
assert(
releasable_amount <= (stream.total_amount - stream.released_amount),
"Releasable amount exceeds remaining tokens"
);

self.streams.write(
stream_key,
stream_id,
Stream {
released_amount: stream.released_amount + releasable_amount,
..stream
Expand All @@ -104,15 +116,13 @@ pub mod erc20_streaming {
}

fn releasable_amount(&self, stream: Stream) -> felt252 {
let current_time = starknet::get_block_timestamp();
if current_time >= stream.end_time {
return stream.total_amount - stream.released_amount;
} else {
let current_time = starknet::get_block_timestamp();
let time_elapsed = current_time - stream.start_time;
let vesting_duration = stream.end_time - stream.start_time;
let vested_amount = stream.total_amount * time_elapsed / vesting_duration;
return vested_amount - stream.released_amount;

let vested_amount = stream.total_amount * min(time_elapsed, vesting_duration) / vesting_duration;;
vested_amount - stream.released_amount;
}
}
}
}

43 changes: 37 additions & 6 deletions src/applications/erc20.md
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,20 +1,51 @@
# ERC20 Token

Contracts that follow the [ERC20 Standard](https://eips.ethereum.org/EIPS/eip-20) are called ERC20 tokens. They are used to represent fungible assets.
Contracts that follow the [ERC20 Standard](https://eips.ethereum.org/EIPS/eip-20) are called ERC20 tokens. They are used to represent fungible assets, and are fundamental in decentralized applications for representing tradable assets, such as currencies or utility tokens.

To create an ERC20 contract, it must implement the following interface:

```rust
```
{{#include ../../listings/applications/erc20/src/token.cairo:interface}}
```

In Starknet, function names should be written in _snake_case_. This is not the case in Solidity, where function names are written in _camelCase_.
The Starknet ERC20 interface is therefore slightly different from the Solidity ERC20 interface.
In Starknet, function names should be written in _snake_case_. This is not the case in Solidity, where function names are written in _camelCase_. As a result, the Starknet ERC20 interface is slightly different from the Solidity ERC20 interface, though it maintains the same core functionalities for minting, transferring, and approving tokens.

### ERC20 Implementation in Cairo

Here's an implementation of the ERC20 interface in Cairo:

```rust
```
{{#include ../../listings/applications/erc20/src/token.cairo:erc20}}
```

There's several other implementations, such as the [Open Zeppelin](https://docs.openzeppelin.com/contracts-cairo/0.7.0/erc20) or the [Cairo By Example](https://cairo-by-example.com/examples/erc20/) ones.
The above implementation showcases the basic structure required for ERC20 tokens on Starknet. Starknet's native Cairo language enables handling token functionalities in a highly scalable and efficient manner, benefiting from Cairo's zero-knowledge architecture.

## Token Streaming Extension

In addition to basic ERC20 functionality, the contract can also be extended with additional features such as token streaming. Token streaming allows gradual distribution of tokens over time, making it suitable for vesting scenarios.

This extension includes:

1. **Setting Up Token Streams**: Defining a recipient and total amount, along with start and end times for the token distribution.
2. **Vesting Period Management**: Automatically calculates the vested amount of tokens based on time.
3. **Releasing Tokens**: Allows users to withdraw tokens as they become vested.

Here is a basic function used to calculate the amount of tokens available for release:

```
fn releasable_amount(&self, stream: Stream) -> felt252 {
let current_time = starknet::get_block_timestamp();
let time_elapsed = current_time - stream.start_time;
let vesting_duration = stream.end_time - stream.start_time;
let vested_amount = stream.total_amount * min(time_elapsed, vesting_duration) / vesting_duration;
vested_amount - stream.released_amount
}
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved
```

This function dynamically calculates the amount of tokens that have vested and are available for release based on the elapsed time since the start of the stream.

## Further Resources

For other implementations and variations of ERC20, there are several notable libraries and examples:
- The [OpenZeppelin Cairo ERC20](https://docs.openzeppelin.com/contracts-cairo/0.7.0/erc20) library provides battle-tested contracts for ERC20 functionality in Starknet.
- The [Cairo By Example](https://cairo-by-example.com/examples/erc20/) repository offers detailed explanations and examples of ERC20 implementation in Cairo.