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

TokenLock Vesting Smart Contract #1

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions securelock/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

settings/Mainnet.toml
settings/Testnet.toml
history.txt
4 changes: 4 additions & 0 deletions securelock/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

{
"deno.enable": true,
}
18 changes: 18 additions & 0 deletions securelock/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

{
"version": "2.0.0",
"tasks": [
{
"label": "check contracts",
"group": "test",
"type": "shell",
"command": "clarinet check"
},
{
"label": "test contracts",
"group": "test",
"type": "shell",
"command": "clarinet test"
}
]
}
22 changes: 22 additions & 0 deletions securelock/Clarinet.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[project]
name = "securelock"
authors = []
description = ""
telemetry = true
requirements = []
[contracts.tokensafe]
path = "contracts/tokensafe.clar"
depends_on = []

[repl]
costs_version = 2
parser_version = 2

[repl.analysis]
passes = ["check_checker"]

[repl.analysis.check_checker]
strict = false
trusted_sender = false
trusted_caller = false
callee_filter = false
84 changes: 84 additions & 0 deletions securelock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Vesting Smart Contract

## Overview

This Clarity smart contract implements a token vesting system designed for securing team or advisor tokens, ensuring long-term commitment, and managing vesting distribution over time to various participants. The contract allows for the creation of customized vesting schedules, token claiming, and transfers of vested tokens.

## Features

- Token initialization with custom name, symbol, and decimals
- Creation of personalized vesting schedules for participants
- Vesting period with customizable start time, cliff period, and vesting duration
- Calculation of vested tokens based on the current block height
- Claiming of vested tokens by participants
- Transfer of claimed tokens between participants
- Querying of token balances, vesting schedules, and contract information

## Contract Structure

The smart contract consists of the following main components:

1. Data Variables:
- Token information (name, symbol, decimals)
- Total supply
- Contract initialization status

2. Data Maps:
- Token balances
- Vesting schedules

3. Public Functions:
- `initialize`: Set up the token with basic information
- `create-vesting-schedule`: Create a vesting schedule for a participant
- `claim-vested-tokens`: Allow participants to claim their vested tokens
- `transfer`: Enable transferring of claimed tokens

4. Read-Only Functions:
- `get-vested-amount`: Calculate the amount of tokens vested for a participant
- `get-balance`: Retrieve the token balance of an account
- `get-total-supply`: Get the total token supply
- `get-name`, `get-symbol`, `get-decimals`: Retrieve token information
- `get-vesting-schedule`: Get the vesting schedule for a participant
- `is-initialized`: Check if the contract has been initialized

## Setup and Deployment

To set up and deploy this smart contract:

1. Ensure you have [Clarinet](https://github.com/hirosystems/clarinet) installed.
2. Create a new Clarinet project: `clarinet new vesting-contract`
3. Replace the contents of `contracts/vesting-contract.clar` with the provided smart contract code.
4. Update the `Clarinet.toml` file to include the contract:

```toml
[contracts.vesting-contract]
path = "contracts/vesting-contract.clar"
clarity_version = 2
epoch = 2.4
```

5. Test the contract locally: `clarinet test`
6. Deploy the contract to a testnet or mainnet using Clarinet or other Stacks deployment tools.

## Security Considerations

- Only the contract owner can initialize the contract and create vesting schedules.
- Participants can only claim tokens that have vested according to their schedule.
- The contract uses various checks and balances to prevent unauthorized actions and ensure the integrity of the vesting process.

## Testing

Comprehensive unit tests should be written to cover all functions and edge cases. Use Clarinet's testing framework to write and run tests:


## Limitations and Future Improvements

- The contract doesn't support token minting or burning.
- There's no functionality to modify or revoke vesting schedules once created.
- Consider adding events (using `print`) to log important actions for off-chain tracking.
- Implement a function to allow participants to check their claimable token amount directly.


## Author

[Amobi Ndubuisi or [email protected]]
179 changes: 179 additions & 0 deletions securelock/contracts/tokensafe.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
;; vesting-contract.clar

;; Define constants
(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_UNAUTHORIZED (err u100))
(define-constant ERR_ALREADY_INITIALIZED (err u101))
(define-constant ERR_NOT_INITIALIZED (err u102))
(define-constant ERR_NO_VESTING_SCHEDULE (err u103))
(define-constant ERR_INSUFFICIENT_BALANCE (err u104))
(define-constant ERR_INVALID_PARAMETER (err u105))
(define-constant ERR_TRANSFER_FAILED (err u106))
(define-constant ERR_ALREADY_HAS_SCHEDULE (err u107))
(define-constant ERR_INVALID_RECIPIENT (err u108))

;; Define data variables
(define-data-var token-name (string-ascii 32) "")
(define-data-var token-symbol (string-ascii 32) "")
(define-data-var token-decimals uint u0)
(define-data-var total-supply uint u0)
(define-data-var contract-initialized bool false)

;; Define data maps
(define-map token-balances principal uint)
(define-map vesting-schedules
principal
{
total-allocation: uint,
start-block: uint,
cliff-duration: uint,
vesting-duration: uint,
vesting-interval: uint,
amount-claimed: uint
}
)

;; Initialize the contract
(define-public (initialize (name (string-ascii 32)) (symbol (string-ascii 32)) (decimals uint))
(begin
(asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
(asserts! (not (var-get contract-initialized)) ERR_ALREADY_INITIALIZED)
(asserts! (and (> (len name) u0) (<= (len name) u32)) ERR_INVALID_PARAMETER)
(asserts! (and (> (len symbol) u0) (<= (len symbol) u32)) ERR_INVALID_PARAMETER)
(asserts! (<= decimals u18) ERR_INVALID_PARAMETER)
(var-set token-name name)
(var-set token-symbol symbol)
(var-set token-decimals decimals)
(var-set contract-initialized true)
(ok true)
)
)

;; Create a vesting schedule for a participant
(define-public (create-vesting-schedule
(participant principal)
(total-allocation uint)
(start-block uint)
(cliff-duration uint)
(vesting-duration uint)
(vesting-interval uint)
)
(begin
(asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
(asserts! (var-get contract-initialized) ERR_NOT_INITIALIZED)
(asserts! (is-none (map-get? vesting-schedules participant)) ERR_ALREADY_HAS_SCHEDULE)
(asserts! (> total-allocation u0) ERR_INVALID_PARAMETER)
(asserts! (>= start-block block-height) ERR_INVALID_PARAMETER)
(asserts! (>= vesting-duration cliff-duration) ERR_INVALID_PARAMETER)
(asserts! (> vesting-interval u0) ERR_INVALID_PARAMETER)
(asserts! (<= vesting-interval vesting-duration) ERR_INVALID_PARAMETER)
(map-set vesting-schedules participant {
total-allocation: total-allocation,
start-block: start-block,
cliff-duration: cliff-duration,
vesting-duration: vesting-duration,
vesting-interval: vesting-interval,
amount-claimed: u0
})
(var-set total-supply (+ (var-get total-supply) total-allocation))
(ok true)
)
)

;; Calculate vested amount for a participant
(define-read-only (get-vested-amount (participant principal))
(let (
(schedule (unwrap! (map-get? vesting-schedules participant) ERR_NO_VESTING_SCHEDULE))
(current-block block-height)
(vesting-start (+ (get start-block schedule) (get cliff-duration schedule)))
(vesting-end (+ (get start-block schedule) (get vesting-duration schedule)))
)
(if (>= current-block vesting-end)
(ok (get total-allocation schedule))
(if (< current-block vesting-start)
(ok u0)
(let (
(vested-periods (/ (- current-block vesting-start) (get vesting-interval schedule)))
(vesting-ratio (/ (* vested-periods (get vesting-interval schedule)) (get vesting-duration schedule)))
)
(ok (/ (* (get total-allocation schedule) vesting-ratio) u100))
)
)
)
)
)

;; Claim vested tokens
(define-public (claim-vested-tokens)
(let (
(participant tx-sender)
(schedule (unwrap! (map-get? vesting-schedules participant) ERR_NO_VESTING_SCHEDULE))
(vested-amount (unwrap! (get-vested-amount participant) ERR_INVALID_PARAMETER))
(claimable-amount (- vested-amount (get amount-claimed schedule)))
)
(asserts! (> claimable-amount u0) ERR_INSUFFICIENT_BALANCE)
(map-set vesting-schedules participant
(merge schedule { amount-claimed: vested-amount })
)
(map-set token-balances participant
(+ (default-to u0 (map-get? token-balances participant)) claimable-amount)
)
(ok claimable-amount)
)
)

;; Get balance of a participant
(define-read-only (get-balance (account principal))
(ok (default-to u0 (map-get? token-balances account)))
)

;; Transfer tokens (only for claimed tokens)
(define-public (transfer (amount uint) (recipient principal))
(let (
(sender tx-sender)
(sender-balance (unwrap! (get-balance sender) ERR_INVALID_PARAMETER))
)
(asserts! (and (not (is-eq sender recipient)) (not (is-eq recipient (as-contract tx-sender)))) ERR_INVALID_RECIPIENT)
(asserts! (>= sender-balance amount) ERR_INSUFFICIENT_BALANCE)
(try! (as-contract (transfer-tokens sender recipient amount)))
(ok true)
)
)

(define-private (transfer-tokens (sender principal) (recipient principal) (amount uint))
(begin
(map-set token-balances sender (- (unwrap! (get-balance sender) ERR_INVALID_PARAMETER) amount))
(map-set token-balances recipient (+ (unwrap! (get-balance recipient) ERR_INVALID_PARAMETER) amount))
(ok true)
)
)

;; Get total supply
(define-read-only (get-total-supply)
(ok (var-get total-supply))
)

;; Get token name
(define-read-only (get-name)
(ok (var-get token-name))
)

;; Get token symbol
(define-read-only (get-symbol)
(ok (var-get token-symbol))
)

;; Get token decimals
(define-read-only (get-decimals)
(ok (var-get token-decimals))
)

;; Get vesting schedule for a participant
(define-read-only (get-vesting-schedule (participant principal))
(ok (map-get? vesting-schedules participant))
)

;; Check if the contract is initialized
(define-read-only (is-initialized)
(ok (var-get contract-initialized))
)
Loading