From 3b888994571790fa3ebcc8f4c032c1ecb29f035c Mon Sep 17 00:00:00 2001 From: teefeh-07 Date: Tue, 17 Jun 2025 18:42:22 +0000 Subject: [PATCH] feat: enhance GovStack token validation system - Fix syntax error in real.clar contract - Add enhanced validation logic with multiple security checks - Implement comprehensive error handling with new error types - Add read-only functions for validation status checking - Create complete test suite with edge cases - Update project metadata in Clarinet.toml - Enhance documentation with detailed API reference Security improvements: - Added principal format validation - Enhanced null/empty principal checks - Improved error specificity for better debugging - Added pre-validation assertions Testing improvements: - Comprehensive test coverage for all functions - Error case testing - Read-only function validation - Edge case scenarios --- Clarinet.toml | 4 +- README.md | 43 ++++++++++++++++--- contracts/real.clar | 54 ++++++++++++++++++------ tests/real_test.ts | 100 ++++++++++++++++++++++++++++++++++++++------ 4 files changed, 169 insertions(+), 32 deletions(-) diff --git a/Clarinet.toml b/Clarinet.toml index c0a295b..faca8d8 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -1,7 +1,7 @@ [project] name = "GovStack" -authors = [] -description = "" +authors = ["teefeh-07 "] +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" diff --git a/README.md b/README.md index e19a704..38173e0 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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: diff --git a/contracts/real.clar b/contracts/real.clar index 0fc7115..dee808e 100644 --- a/contracts/real.clar +++ b/contracts/real.clar @@ -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 \ No newline at end of file +) + +;; 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))) \ No newline at end of file diff --git a/tests/real_test.ts b/tests/real_test.ts index 561c544..64eb8f3 100644 --- a/tests/real_test.ts +++ b/tests/real_test.ts @@ -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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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); }, });