Skip to content
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: 2 additions & 2 deletions Clarinet.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "GovStack"
authors = []
description = ""
authors = ["teefeh-07 <boluwatifeoniyilo@gmail.com>"]
description = "A comprehensive token validation smart contract system for the Stacks blockchain using Clarity. Provides secure principal validation with enhanced error handling and multiple validation approaches."
telemetry = true
requirements = []
cache_dir = "/home/runner/Real/GovStack/./.requirements"
Expand Down
43 changes: 38 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ This smart contract implements a token validation system on the Stacks blockchai

### Error Constants
```clarity
ERR-INVALID-TOKEN (err u1) // Used when token validation fails
ERR-VALIDATION-FAILED (err u2) // Used for general validation failures
ERR-INVALID-TOKEN (err u1) // Used when token validation fails
ERR-VALIDATION-FAILED (err u2) // Used for general validation failures
ERR-UNAUTHORIZED (err u3) // Used for unauthorized access attempts
ERR-INVALID-PRINCIPAL (err u4) // Used for invalid principal format
```

### Core Functions
Expand Down Expand Up @@ -51,7 +53,25 @@ Alternative public function with direct matching approach.
- `new-token`: Principal to process
- **Returns**: Response containing either:
- Success: u1
- Error: ERR-VALIDATION-FAILED
- Error: ERR-VALIDATION-FAILED or ERR-INVALID-PRINCIPAL

#### `validate-principal`
Read-only function to check principal validity without state changes.
```clarity
(define-read-only (validate-principal (principal-to-check principal)))
```
- **Parameters**:
- `principal-to-check`: Principal to validate
- **Returns**: Boolean indicating validity

#### `get-validation-status`
Read-only function to get comprehensive validation status.
```clarity
(define-read-only (get-validation-status (token principal)))
```
- **Parameters**:
- `token`: Principal to check
- **Returns**: Response containing validation result

## Usage

Expand Down Expand Up @@ -88,8 +108,21 @@ Alternative public function with direct matching approach.

### Testing
1. Clone the repository
2. Deploy the contract to a local Stacks node
3. Run test cases against the deployed contract
2. Install Clarinet (if not already installed)
3. Run the test suite:
```bash
clarinet test
```
4. Check contract syntax:
```bash
clarinet check
```

The test suite includes:
- Successful token validation tests
- Error handling validation
- Read-only function testing
- Edge case scenarios

### Deployment
1. Build the contract:
Expand Down
54 changes: 42 additions & 12 deletions contracts/real.clar
Original file line number Diff line number Diff line change
@@ -1,35 +1,65 @@
;; Define error codes
(define-constant ERR-INVALID-TOKEN (err u1))
(define-constant ERR-VALIDATION-FAILED (err u2))
(define-constant ERR-UNAUTHORIZED (err u3))
(define-constant ERR-INVALID-PRINCIPAL (err u4))

;; Token validation utility function with explicit response type
(define-private (comprehensive-token-validation (token principal))
;; For demonstration, validating if principal is not null
;; In a real implementation, you would add more validation logic
(if (is-eq token tx-sender) ;; Example validation comparing with tx-sender
;; Enhanced validation logic with multiple checks
(if (and
;; Check if token is not null/empty
(not (is-eq token 'SP000000000000000000002Q6VF78))
;; Check if token equals transaction sender
(is-eq token tx-sender)
;; Additional validation: ensure principal is valid format
(> (len (principal-to-string token)) u0))
(ok token)
ERR-INVALID-TOKEN))

;; Helper function to validate principal format
(define-private (is-valid-principal (principal-to-check principal))
(let ((principal-str (principal-to-string principal-to-check)))
(and
(> (len principal-str) u0)
(not (is-eq principal-to-check 'SP000000000000000000002Q6VF78)))))

;; First approach: Direct validation with match
(define-public (process-new-token (new-token principal))
(let
(
;; Store the validation result
(validation-result (comprehensive-token-validation new-token))
(begin
;; Pre-validation check
(asserts! (is-valid-principal new-token) ERR-INVALID-PRINCIPAL)
(let
(
;; Store the validation result
(validation-result (comprehensive-token-validation new-token))
)
;; Check the validation result
(match validation-result
success (ok u1)
error ERR-VALIDATION-FAILED)
)
;; Check the validation result
(match validation-result
success (ok u1)
error ERR-VALIDATION-FAILED)
)
)

;; Second approach: Alternative validation method
(define-public (process-new-token-alt (new-token principal))
(begin
;; Pre-validation check
(asserts! (is-valid-principal new-token) ERR-INVALID-PRINCIPAL)
;; Validate token with direct matching
(match (comprehensive-token-validation new-token)
success (ok u1)
error ERR-VALIDATION-FAILED)
)
)git
)

;; Read-only function to check if a principal is valid without state changes
(define-read-only (validate-principal (principal-to-check principal))
(is-valid-principal principal-to-check))

;; Read-only function to get validation status
(define-read-only (get-validation-status (token principal))
(match (comprehensive-token-validation token)
success (ok true)
error (ok false)))
100 changes: 87 additions & 13 deletions tests/real_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,98 @@ import { Clarinet, Tx, Chain, Account, types } from 'https://deno.land/x/clarine
import { assertEquals } from 'https://deno.land/std@0.90.0/testing/asserts.ts';

Clarinet.test({
name: "Ensure that <...>",
name: "Test successful token validation with process-new-token",
async fn(chain: Chain, accounts: Map<string, Account>) {
const deployer = accounts.get('deployer')!;
const wallet1 = accounts.get('wallet_1')!;

let block = chain.mineBlock([
Tx.contractCall('real', 'process-new-token', [types.principal(wallet1.address)], wallet1.address)
]);

assertEquals(block.receipts.length, 1);
assertEquals(block.receipts[0].result, '(ok u1)');
assertEquals(block.height, 2);
},
});

Clarinet.test({
name: "Test successful token validation with process-new-token-alt",
async fn(chain: Chain, accounts: Map<string, Account>) {
const deployer = accounts.get('deployer')!;
const wallet1 = accounts.get('wallet_1')!;

let block = chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
Tx.contractCall('real', 'process-new-token-alt', [types.principal(wallet1.address)], wallet1.address)
]);
assertEquals(block.receipts.length, 0);

assertEquals(block.receipts.length, 1);
assertEquals(block.receipts[0].result, '(ok u1)');
assertEquals(block.height, 2);
},
});

Clarinet.test({
name: "Test failed validation when token doesn't match tx-sender",
async fn(chain: Chain, accounts: Map<string, Account>) {
const deployer = accounts.get('deployer')!;
const wallet1 = accounts.get('wallet_1')!;
const wallet2 = accounts.get('wallet_2')!;

let block = chain.mineBlock([
Tx.contractCall('real', 'process-new-token', [types.principal(wallet2.address)], wallet1.address)
]);

assertEquals(block.receipts.length, 1);
assertEquals(block.receipts[0].result, '(err u2)'); // ERR-VALIDATION-FAILED
assertEquals(block.height, 2);
},
});

Clarinet.test({
name: "Test read-only validate-principal function",
async fn(chain: Chain, accounts: Map<string, Account>) {
const deployer = accounts.get('deployer')!;
const wallet1 = accounts.get('wallet_1')!;

let result = chain.callReadOnlyFn('real', 'validate-principal', [types.principal(wallet1.address)], wallet1.address);
assertEquals(result.result, 'true');

// Test with null principal (should fail)
result = chain.callReadOnlyFn('real', 'validate-principal', [types.principal('SP000000000000000000002Q6VF78')], wallet1.address);
assertEquals(result.result, 'false');
},
});

Clarinet.test({
name: "Test read-only get-validation-status function",
async fn(chain: Chain, accounts: Map<string, Account>) {
const deployer = accounts.get('deployer')!;
const wallet1 = accounts.get('wallet_1')!;
const wallet2 = accounts.get('wallet_2')!;

// Test with matching principal (should return true)
let result = chain.callReadOnlyFn('real', 'get-validation-status', [types.principal(wallet1.address)], wallet1.address);
assertEquals(result.result, '(ok true)');

// Test with non-matching principal (should return false)
result = chain.callReadOnlyFn('real', 'get-validation-status', [types.principal(wallet2.address)], wallet1.address);
assertEquals(result.result, '(ok false)');
},
});

block = chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
Clarinet.test({
name: "Test error handling for invalid principal",
async fn(chain: Chain, accounts: Map<string, Account>) {
const deployer = accounts.get('deployer')!;
const wallet1 = accounts.get('wallet_1')!;

let block = chain.mineBlock([
Tx.contractCall('real', 'process-new-token', [types.principal('SP000000000000000000002Q6VF78')], wallet1.address)
]);
assertEquals(block.receipts.length, 0);
assertEquals(block.height, 3);

assertEquals(block.receipts.length, 1);
assertEquals(block.receipts[0].result, '(err u4)'); // ERR-INVALID-PRINCIPAL
assertEquals(block.height, 2);
},
});