From 0272fc8040249c4cf9d7837469eb7e9af378943f Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Tue, 3 Feb 2026 12:43:10 +0100 Subject: [PATCH 1/3] smartcontract deployed --- DEPLOYMENT.MD | 4 - backend/BACKEND_TESTING_QUICK_REFERENCE.md | 160 --------------- backend/check-verification.ts | 36 ---- backend/lib/verification-store.ts | 28 --- backend/test-backend.ts | 225 --------------------- backend/verify.ts | 105 ---------- contracts/AaveV3Strategy.sol | 34 ++-- contracts/AttestifyVault.sol | 88 +++----- contracts/SelfProtocolVerifier.sol | 206 ------------------- contracts/mocks/MockVerifier.sol | 5 +- scripts/deploy-vault.ts | 41 ++-- 11 files changed, 65 insertions(+), 867 deletions(-) delete mode 100644 backend/BACKEND_TESTING_QUICK_REFERENCE.md delete mode 100644 backend/check-verification.ts delete mode 100644 backend/lib/verification-store.ts delete mode 100644 backend/test-backend.ts delete mode 100644 backend/verify.ts delete mode 100644 contracts/SelfProtocolVerifier.sol diff --git a/DEPLOYMENT.MD b/DEPLOYMENT.MD index ee36eeb..e69de29 100644 --- a/DEPLOYMENT.MD +++ b/DEPLOYMENT.MD @@ -1,4 +0,0 @@ - VERIFIER_ADDRESS=0x5165370c71aacd3276cb18178812a4cc6541840c - STRATEGY_ADDRESS=0x5a8433c77871530840d769c2d3aee39196c1214f - VAULT_IMPL_ADDRESS=0xbf277f1e43d825a481fe807ab145f812a34233e6 - VAULT_PROXY_ADDRESS=0x00255f3452266d79d829662ff81d9f55f7a2eab9 \ No newline at end of file diff --git a/backend/BACKEND_TESTING_QUICK_REFERENCE.md b/backend/BACKEND_TESTING_QUICK_REFERENCE.md deleted file mode 100644 index 9bdae5c..0000000 --- a/backend/BACKEND_TESTING_QUICK_REFERENCE.md +++ /dev/null @@ -1,160 +0,0 @@ -# Backend Testing - Quick Reference - -Your backend is **production-ready** to test! Here's how to validate it works **without frontend**: - ---- - -## šŸš€ Fastest Way: cURL (2 minutes) - -```bash -# Terminal 1: Start your backend -npm run dev - -# Terminal 2: Test verification endpoint -curl -X POST http://localhost:3000/api/verify \ - -H "Content-Type: application/json" \ - -d '{ - "attestationId": 1, - "proof": {"pi_a":[],"pi_b":[],"pi_c":[]}, - "publicSignals": ["hash1","hash2"], - "userContextData": "0x1234567890123456789012345678901234567890" - }' - -# Terminal 2: Test check endpoint -curl -X POST http://localhost:3000/api/check-verification \ - -H "Content-Type: application/json" \ - -d '{"userId":"0x1234567890123456789012345678901234567890"}' -``` - -**You should see:** -- `POST /api/verify` → `{"status":"success","result":true}` or error message -- `POST /api/check-verification` → `{"verified":true,"userId":"0x..."}` - ---- - -## šŸ“¦ Three Testing Tools Provided - -| Tool | Best For | Command | -|------|----------|---------| -| **cURL** | Quick manual testing | `curl -X POST ...` | -| **Bash Script** | Automated testing | `./backend/test-backend.sh` | -| **Node Script** | Detailed testing | `node --loader ts-node/esm backend/test-backend.ts` | -| **Postman** | Visual testing | Import JSON collection | - ---- - -## šŸ“ Files Created - -1. **[TESTING.md](./TESTING.md)** - Complete testing guide with all details -2. **[test-backend.sh](./test-backend.sh)** - Automated bash testing script -3. **[test-backend.ts](./test-backend.ts)** - Node.js testing utility -4. **[Self-Protocol-Backend-Tests.postman_collection.json](./Self-Protocol-Backend-Tests.postman_collection.json)** - Postman collection -5. **[BACKEND_TESTING_QUICK_REFERENCE.md](./BACKEND_TESTING_QUICK_REFERENCE.md)** - This file - ---- - -## āœ… Validation Checklist - -Run these commands and verify responses: - -### āœ“ Can API be reached? -```bash -curl http://localhost:3000/api/verify -X POST -``` -Should NOT say "Connection refused" - -### āœ“ Does /api/verify accept proofs? -```bash -curl -X POST http://localhost:3000/api/verify \ - -H "Content-Type: application/json" \ - -d '{"attestationId":1,"proof":{},"publicSignals":[],"userContextData":"0xabc"}' -``` -Should return `{"status":"...","result":...}` - -### āœ“ Does /api/check-verification check users? -```bash -curl -X POST http://localhost:3000/api/check-verification \ - -H "Content-Type: application/json" \ - -d '{"userId":"0xabc"}' -``` -Should return `{"verified":true|false,"userId":"0xabc"}` - ---- - -## šŸŽÆ What's Tested - -| Component | Status | Note | -|-----------|--------|------| -| Backend endpoints exist | āœ… | `/api/verify` and `/api/check-verification` | -| Accept POST requests | āœ… | Correct HTTP method and headers | -| Process proof data | āœ… | Stores by userIdentifier | -| Return verification status | āœ… | Returns verified true/false | -| Nullifier included | āœ… | For sybil resistance | -| Multiple users supported | āœ… | Each user stored independently | - ---- - -## šŸ“‹ What Happens During Tests - -1. **Setup**: Start your backend server -2. **Verify Test**: Simulates Self relayers sending proof → Backend stores it -3. **Check Test**: Simulates miniapp checking user → Backend returns status -4. **Multi-user Test**: Tests that multiple users can be verified independently - ---- - -## šŸ”— Flow Reference - -``` -User Flow: - Frontend calls SelfAppBuilder deeplink - ↓ - Opens Self app → User scans passport - ↓ - Self relayers POST /api/verify with proof - ↓ - Backend stores verification ← [TEST THIS] - ↓ - Self app redirects to deeplinkCallback - ↓ - Frontend calls POST /api/check-verification ← [TEST THIS] - ↓ - Backend returns verified=true + nullifier - ↓ - Frontend stores on-chain (optional) -``` - ---- - -## 🚨 Common Issues & Fixes - -| Issue | Cause | Fix | -|-------|-------|-----| -| "Connection refused" | Backend not running | `npm run dev` | -| "404 Not Found" | Wrong endpoint path | Check route definitions in pages/api/ | -| "Proof validation failed" | Using dummy data (expected!) | Will work with real proofs from Self app | -| Empty `userId` in response | Endpoint not called | Ensure correct JSON body format | - ---- - -## šŸ“ž Ready for Frontend? - -Once you confirm all tests pass, tell your frontend team: - -**They need to implement:** -1. `SelfAppBuilder` with deeplink (no QR code) -2. `window.open(getUniversalLink(selfApp))` to open Self app -3. After user returns, call `POST /api/check-verification` -4. If `verified=true`, optionally store on-chain - -**Your backend endpoints are ready:** -- āœ… `POST /api/verify` - accepts proofs from Self Protocol -- āœ… `POST /api/check-verification` - returns verification status - ---- - -## šŸ“š See Also - -- [SELF_INTEGRATION.md](./SELF_INTEGRATION.md) - Backend setup & flow details -- [TESTING.md](./TESTING.md) - Comprehensive testing guide -- [../contracts/SelfProtocolVerifier.sol](../contracts/SelfProtocolVerifier.sol) - On-chain verification contract diff --git a/backend/check-verification.ts b/backend/check-verification.ts deleted file mode 100644 index 5f62f25..0000000 --- a/backend/check-verification.ts +++ /dev/null @@ -1,36 +0,0 @@ -// pages/api/check-verification.ts (Next.js API route) -// Called by miniapp when user returns from Self app (deeplinkCallback redirect). -// Checks if the user was verified by Self relayers via /api/verify. - -type ApiRequest = { method?: string; body?: Record }; -type ApiResponse = { status: (code: number) => ApiResponse; json: (body: unknown) => unknown }; -import { getVerification } from './lib/verification-store.js'; - -export default async function handler(req: ApiRequest, res: ApiResponse) { - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method not allowed' }); - } - - const { userId } = req.body ?? {}; - - if (!userId || typeof userId !== 'string') { - return res.status(400).json({ error: 'userId is required' }); - } - - const record = getVerification(userId); - - if (!record) { - return res.status(200).json({ - verified: false, - userId, - }); - } - - return res.status(200).json({ - verified: true, - userId, - verifiedAt: record.verifiedAt, - nullifier: record.nullifier, - sessionId: req.body?.sessionId, // Echo back if frontend passed it - }); -} diff --git a/backend/lib/verification-store.ts b/backend/lib/verification-store.ts deleted file mode 100644 index cd1e884..0000000 --- a/backend/lib/verification-store.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Verification storage for Self Protocol deeplink flow. - * Replace with your database (Postgres, Redis, etc.) in production. - * - * Flow: Self relayers call /api/verify → we store by userIdentifier. - * Miniapp calls /api/check-verification after user returns from Self app. - */ -export interface VerificationRecord { - userIdentifier: string; - nullifier: string; - verifiedAt: number; - attestationId: number; - nationality?: string; -} - -const verifiedUsers = new Map(); - -export function storeVerification(record: VerificationRecord): void { - verifiedUsers.set(record.userIdentifier.toLowerCase(), record); -} - -export function getVerification(userIdentifier: string): VerificationRecord | undefined { - return verifiedUsers.get(userIdentifier.toLowerCase()); -} - -export function isVerified(userIdentifier: string): boolean { - return verifiedUsers.has(userIdentifier.toLowerCase()); -} diff --git a/backend/test-backend.ts b/backend/test-backend.ts deleted file mode 100644 index 6678f87..0000000 --- a/backend/test-backend.ts +++ /dev/null @@ -1,225 +0,0 @@ -/** - * Backend Testing Utility for Self Protocol Integration - * - * This script tests the backend without needing the frontend. - * It simulates what the Self relayers would send and what the miniapp would call. - * - * Prerequisites: - * 1. Backend API running (Next.js dev server on http://localhost:3000) - * 2. Environment variables set in .env (.env.local for Next.js) - */ - -import fetch from 'node-fetch'; - -const API_BASE_URL = process.env.API_URL || 'http://localhost:3000'; - -// Mock data similar to what Self relayers would send -const MOCK_PROOF_DATA = { - attestationId: 1, // Passport = 1, EU ID = 2, Aadhaar = 3 - proof: { - pi_a: [ - '1234567890123456789012345678901234567890123456789012345678901234', - '1234567890123456789012345678901234567890123456789012345678901234', - ], - pi_b: [ - [ - '1234567890123456789012345678901234567890123456789012345678901234', - '1234567890123456789012345678901234567890123456789012345678901234', - ], - [ - '1234567890123456789012345678901234567890123456789012345678901234', - '1234567890123456789012345678901234567890123456789012345678901234', - ], - ], - pi_c: [ - '1234567890123456789012345678901234567890123456789012345678901234', - '1234567890123456789012345678901234567890123456789012345678901234', - ], - }, - publicSignals: [ - '12345678901234567890123456789012', // nullifier - '98765432109876543210987654321098', // userIdentifier - ], - userContextData: '0x1234567890123456789012345678901234567890', // wallet address -}; - -const TEST_USER_ADDRESS = '0x1234567890123456789012345678901234567890'; - -/** - * Step 1: Simulate Self relayers calling POST /api/verify - */ -async function testVerifyEndpoint(): Promise { - console.log('\nšŸ“ Step 1: Testing POST /api/verify'); - console.log(' Simulating Self relayers sending proof data...\n'); - - try { - const response = await fetch(`${API_BASE_URL}/api/verify`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(MOCK_PROOF_DATA), - }); - - const data = await response.json() as { status: string; result: boolean; reason?: string }; - - console.log(` Status: ${response.status}`); - console.log(` Response:`, JSON.stringify(data, null, 2)); - - if (data.status === 'success' && data.result === true) { - console.log(' āœ… Verification stored successfully!'); - return true; - } else { - console.log(` āš ļø Verification failed: ${data.reason}`); - return false; - } - } catch (error) { - console.error(` āŒ Error calling /api/verify:`, error); - return false; - } -} - -/** - * Step 2: Simulate miniapp calling POST /api/check-verification after user returns - */ -async function testCheckVerificationEndpoint(userId: string): Promise { - console.log('\nšŸ” Step 2: Testing POST /api/check-verification'); - console.log(` Checking if user ${userId} is verified...\n`); - - try { - const response = await fetch(`${API_BASE_URL}/api/check-verification`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ userId }), - }); - - const data = await response.json() as { - verified: boolean; - userId: string; - verifiedAt?: number; - nullifier?: string; - }; - - console.log(` Status: ${response.status}`); - console.log(` Response:`, JSON.stringify(data, null, 2)); - - if (data.verified) { - console.log(` āœ… User is verified!`); - console.log(` Verified at: ${new Date(data.verifiedAt!).toISOString()}`); - console.log(` Nullifier: ${data.nullifier}`); - return true; - } else { - console.log(` āš ļø User is not verified yet`); - return false; - } - } catch (error) { - console.error(` āŒ Error calling /api/check-verification:`, error); - return false; - } -} - -/** - * Step 3: Test with different user addresses - */ -async function testMultipleUsers(): Promise { - console.log('\nšŸ‘„ Step 3: Testing with multiple users\n'); - - const users = [ - { address: '0xaabbccddaabbccddaabbccddaabbccddaabbccdd', name: 'Alice' }, - { address: '0x1122334411223344112233441122334411223344', name: 'Bob' }, - ]; - - for (const user of users) { - console.log(`\n Testing ${user.name} (${user.address})...`); - - // Create new proof data with this user - const proofData = { - ...MOCK_PROOF_DATA, - userContextData: user.address, - publicSignals: [ - '99999999999999999999999999999999', // different nullifier - user.address.substring(2).padEnd(32, '0'), // user identifier - ], - }; - - // Call verify - const response = await fetch(`${API_BASE_URL}/api/verify`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(proofData), - }); - - const result = await response.json() as { status: string; result: boolean }; - - if (result.status === 'success') { - console.log(` āœ“ ${user.name} verification stored`); - - // Check verification - const checkResponse = await fetch(`${API_BASE_URL}/api/check-verification`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ userId: user.address }), - }); - - const checkResult = await checkResponse.json() as { verified: boolean }; - console.log(` āœ“ ${user.name} check: verified=${checkResult.verified}`); - } - } -} - -/** - * Main test runner - */ -async function runTests(): Promise { - console.log('šŸš€ Self Protocol Backend Integration Tests\n'); - console.log(`API URL: ${API_BASE_URL}`); - console.log('=' .repeat(60)); - - try { - // Test 1: Verify endpoint - const verifySuccess = await testVerifyEndpoint(); - - if (!verifySuccess) { - console.log('\nāŒ Verification failed. Check your backend is running and /api/verify is accessible.'); - console.log('\nTroubleshooting:'); - console.log(' 1. Is your Next.js dev server running? (npm run dev)'); - console.log(' 2. Are your environment variables set in .env.local?'); - console.log(' 3. Check the backend API logs for detailed errors'); - process.exit(1); - } - - // Give it a moment to store - await new Promise(resolve => setTimeout(resolve, 500)); - - // Test 2: Check verification endpoint - const checkSuccess = await testCheckVerificationEndpoint(TEST_USER_ADDRESS); - - if (!checkSuccess) { - console.log('\nāŒ Check verification failed.'); - process.exit(1); - } - - // Test 3: Multiple users - await testMultipleUsers(); - - console.log('\n' + '='.repeat(60)); - console.log('āœ… All tests passed!\n'); - console.log('šŸ“‹ Summary:'); - console.log(' āœ“ POST /api/verify - Accepts and stores proofs from Self relayers'); - console.log(' āœ“ POST /api/check-verification - Returns verification status to miniapp'); - console.log(' āœ“ Multiple users can be verified independently'); - console.log('\nšŸŽÆ Next Steps:'); - console.log(' 1. Frontend team implements SelfAppBuilder with deeplink'); - console.log(' 2. Frontend opens Self app for user verification'); - console.log(' 3. After user returns, frontend calls /api/check-verification'); - console.log(' 4. If verified, store on-chain via SelfProtocolVerifier contract\n'); - } catch (error) { - console.error('āŒ Test suite failed:', error); - process.exit(1); - } -} - -// Run if executed directly -if (import.meta.url === `file://${process.argv[1]}`) { - runTests().catch(console.error); -} - -export { testVerifyEndpoint, testCheckVerificationEndpoint, testMultipleUsers }; diff --git a/backend/verify.ts b/backend/verify.ts deleted file mode 100644 index 2414d26..0000000 --- a/backend/verify.ts +++ /dev/null @@ -1,105 +0,0 @@ -// pages/api/verify.ts (Next.js API route) -// Called by Self Protocol relayers when user completes verification in Self app. -// For deeplink flow: User opens Self app → verifies → relayers POST here → Self app redirects user back to miniapp. - -// Use Next.js types if available; otherwise define minimal compatible types -type ApiRequest = { method?: string; body?: Record }; -type ApiResponse = { status: (code: number) => ApiResponse; json: (body: unknown) => unknown }; -import { - SelfBackendVerifier, - DefaultConfigStore, - type AttestationId, -} from '@selfxyz/core'; - -// Allow passport (1), EU ID (2), Aadhaar (3) -const allowedIds = new Map([ - [1, true], - [2, true], - [3, true], -]); -import { storeVerification } from './lib/verification-store.js'; - -const SELF_SCOPE = process.env.SELF_SCOPE || 'liquifi-miniapp-v1'; -const SELF_ENDPOINT = process.env.SELF_ENDPOINT || 'https://your-domain.com/api/verify'; -const MOCK_PASSPORT = process.env.SELF_MOCK_PASSPORT === 'true'; // true = Celo Sepolia testnet -const USER_ID_TYPE = (process.env.SELF_USER_ID_TYPE || 'hex') as 'hex' | 'uuid'; - -// Verification config - MUST match frontend SelfAppBuilder disclosures -const excludedList = process.env.SELF_EXCLUDED_COUNTRIES - ? process.env.SELF_EXCLUDED_COUNTRIES.split(',').map((c) => c.trim()) - : ['IRN', 'PRK', 'RUS', 'SYR']; - -const configStore = new DefaultConfigStore({ - minimumAge: Number(process.env.SELF_MIN_AGE || 18), - // @ts-expect-error - Country3LetterCode[] expects literal types; runtime accepts valid ISO codes - excludedCountries: excludedList, - ofac: process.env.SELF_OFAC !== 'false', -}); - -const verifier = new SelfBackendVerifier( - SELF_SCOPE, - SELF_ENDPOINT, - MOCK_PASSPORT, - allowedIds, - configStore, - USER_ID_TYPE -); - -export default async function handler(req: ApiRequest, res: ApiResponse) { - if (req.method !== 'POST') { - return res.status(405).json({ status: 'error', result: false, reason: 'Method not allowed' }); - } - - try { - const body = req.body ?? {}; - const { attestationId, proof, publicSignals, userContextData } = body as { - attestationId?: number; - proof?: unknown; - publicSignals?: unknown; - userContextData?: string; - }; - - if (!proof || !publicSignals || !attestationId || !userContextData) { - return res.status(200).json({ - status: 'error', - result: false, - reason: 'Proof, publicSignals, attestationId and userContextData are required', - }); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const result = await verifier.verify(attestationId as AttestationId, proof as any, publicSignals as any, userContextData); - - const { isValid, isMinimumAgeValid, isOfacValid } = result.isValidDetails; - - if (!isValid || !isMinimumAgeValid || isOfacValid) { - let reason = 'Verification failed'; - if (!isMinimumAgeValid) reason = 'Minimum age verification failed'; - if (isOfacValid) reason = 'OFAC verification failed'; - return res.status(200).json({ status: 'error', result: false, reason }); - } - - // Store verification for check-verification endpoint - // userIdentifier is wallet address (hex) or uuid depending on frontend config - storeVerification({ - userIdentifier: result.userData.userIdentifier, - nullifier: result.discloseOutput.nullifier, - verifiedAt: Date.now(), - attestationId: result.attestationId, - nationality: result.discloseOutput.nationality || undefined, - }); - - return res.status(200).json({ - status: 'success', - result: true, - }); - } catch (error: unknown) { - console.error('Verification error:', error); - const reason = error instanceof Error ? error.message : 'Unknown error'; - return res.status(200).json({ - status: 'error', - result: false, - reason, - }); - } -} diff --git a/contracts/AaveV3Strategy.sol b/contracts/AaveV3Strategy.sol index fb65f71..dbfd93b 100644 --- a/contracts/AaveV3Strategy.sol +++ b/contracts/AaveV3Strategy.sol @@ -27,10 +27,9 @@ contract AaveV3Strategy is Ownable { IERC20 public immutable aToken; // acUSD token IPool public immutable aavePool; IPoolAddressesProvider public immutable addressesProvider; - - // FIX C-01: Made vault immutable to prevent uninitialized state - // This ensures vault is always set at deployment and cannot be changed - address public immutable vault; + + // Vault contract address (can be set after deployment if needed) + address public vault; /* ========== EVENTS ========== */ @@ -52,8 +51,8 @@ contract AaveV3Strategy is Ownable { * @param _asset Underlying asset (cUSD) * @param _aToken Aave interest-bearing token (acUSD) * @param _addressesProvider Aave PoolAddressesProvider - * @param _vault Vault contract address (REQUIRED - cannot be zero) - * @dev FIX C-01: Vault is now required in constructor and immutable + * @param _vault Vault contract address (can be zero, set later via setVault) + * @dev Vault can be updated after deployment via setVault() */ constructor( address _asset, @@ -61,12 +60,11 @@ contract AaveV3Strategy is Ownable { address _addressesProvider, address _vault ) Ownable(msg.sender) { - // FIX C-01: Added vault to zero address check + // Allow vault to be zero address initially (will be set later) if ( _asset == address(0) || _aToken == address(0) || - _addressesProvider == address(0) || - _vault == address(0) + _addressesProvider == address(0) ) { revert ZeroAddress(); } @@ -74,8 +72,8 @@ contract AaveV3Strategy is Ownable { asset = IERC20(_asset); aToken = IERC20(_aToken); addressesProvider = IPoolAddressesProvider(_addressesProvider); - - // FIX C-01: Initialize immutable vault (no longer settable) + + // Set vault (can be zero address, updated via setVault) vault = _vault; // Get Pool address from provider (recommended by Aave) @@ -96,6 +94,16 @@ contract AaveV3Strategy is Ownable { _; } + /** + * @notice Set or update the vault address + * @param _vault New vault address + * @dev Only callable by owner (deployer) + */ + function setVault(address _vault) external onlyOwner { + if (_vault == address(0)) revert ZeroAddress(); + vault = _vault; + } + /* ========== VAULT FUNCTIONS ========== */ /** @@ -112,7 +120,7 @@ contract AaveV3Strategy is Ownable { // FIX M-02: Check aToken balance before and after supply uint256 balanceBefore = aToken.balanceOf(address(this)); - + // Supply to Aave (receives aTokens automatically) aavePool.supply( address(asset), @@ -120,7 +128,7 @@ contract AaveV3Strategy is Ownable { address(this), 0 // No referral code ); - + uint256 balanceAfter = aToken.balanceOf(address(this)); uint256 received = balanceAfter - balanceBefore; diff --git a/contracts/AttestifyVault.sol b/contracts/AttestifyVault.sol index b5ba994..2d3cbd8 100644 --- a/contracts/AttestifyVault.sol +++ b/contracts/AttestifyVault.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/utils/Pausable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -14,7 +12,7 @@ import "./IAave.sol"; /** * @title AttestifyVault * @notice Main vault contract for yield generation with identity verification - * @dev Upgradeable vault using UUPS pattern, integrates with separate strategy contracts + * @dev Non-upgradeable immutable vault, integrates with separate strategy contracts * @dev AUDIT FIXES IMPLEMENTED: * - C-02: Fixed share calculation for first deposit * - H-01: Implemented gas-bounded circuit breaker pattern @@ -28,13 +26,7 @@ import "./IAave.sol"; * - Strategy: Deploys funds to yield sources (Aave) * - Verifier: Handles identity verification (Self Protocol) */ -contract AttestifyVault is - Initializable, - UUPSUpgradeable, - OwnableUpgradeable, - ReentrancyGuardUpgradeable, - PausableUpgradeable -{ +contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { using SafeERC20 for IERC20; /* ========== STATE VARIABLES (DO NOT REORDER) ========== */ @@ -65,7 +57,7 @@ contract AttestifyVault is uint256 public constant MIN_REBALANCE_INTERVAL = 1 hours; // FIX M-01: Minimum time between rebalances uint256 private constant VIRTUAL_SHARES = 1e3; // Virtual liquidity to harden share price uint256 private constant VIRTUAL_ASSETS = 1e3; - + // FIX H-01: Gas limit for strategy calls to prevent unbounded consumption uint256 private constant STRATEGY_GAS_LIMIT = 100000; // 100k gas for external calls @@ -123,42 +115,26 @@ contract AttestifyVault is /* ========== CONSTRUCTOR ========== */ - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - /* ========== INITIALIZER ========== */ - /** - * @notice Initialize vault (called once after proxy deployment) + * @notice Initialize vault (immutable, cannot be changed) * @param _asset Underlying asset (cUSD) * @param _strategy Aave strategy contract - * @param _verifier Self verifier contract + * @param _verifier Self verifier contract (optional, can be zero) * @param _maxUserDeposit Max deposit per user * @param _maxTotalDeposit Max total TVL - * @dev Sets up all contract dependencies and initial parameters + * @dev Non-upgradeable - contract code is permanent and cannot be changed */ - function initialize( + constructor( address _asset, address _strategy, address _verifier, uint256 _maxUserDeposit, uint256 _maxTotalDeposit - ) external initializer { - if ( - _asset == address(0) || - _strategy == address(0) || - _verifier == address(0) - ) { + ) Ownable(msg.sender) { + if (_asset == address(0) || _strategy == address(0)) { revert ZeroAddress(); } - __Ownable_init(msg.sender); - __UUPSUpgradeable_init(); - __ReentrancyGuard_init(); - __Pausable_init(); - asset = IERC20(_asset); strategy = _strategy; verifier = _verifier; @@ -168,23 +144,12 @@ contract AttestifyVault is rebalancer = msg.sender; } - /* ========== UPGRADE AUTHORIZATION ========== */ - - /** - * @notice Authorize contract upgrades - * @param newImplementation Address of new implementation - * @dev Only owner can upgrade the contract - */ - function _authorizeUpgrade( - address newImplementation - ) internal override onlyOwner {} - /** * @notice Get contract version * @return Version string */ function version() external pure returns (string memory) { - return "1.0.1"; // Updated version after audit fixes + return "1.1.0"; // Non-upgradeable immutable version } /* ========== MODIFIERS ========== */ @@ -194,8 +159,11 @@ contract AttestifyVault is * @dev Calls verifier contract to check user status */ modifier onlyVerified() { - if (!ISelfVerifier(verifier).isVerified(msg.sender)) { - revert NotVerified(); + // If no verifier is configured, skip verification (integration removed/optional) + if (verifier != address(0)) { + if (!ISelfVerifier(verifier).isVerified(msg.sender)) { + revert NotVerified(); + } } _; } @@ -406,9 +374,9 @@ contract AttestifyVault is // FIX H-01: Use try-catch with gas limit to prevent unbounded consumption // Circuit breaker: if strategy fails or exceeds gas, return 0 - try IVaultYieldStrategy(strategy).totalAssets{gas: STRATEGY_GAS_LIMIT}() returns ( - uint256 balance - ) { + try + IVaultYieldStrategy(strategy).totalAssets{gas: STRATEGY_GAS_LIMIT}() + returns (uint256 balance) { return balance; } catch { // Strategy call failed - circuit breaker activates @@ -451,7 +419,7 @@ contract AttestifyVault is */ function _convertToShares(uint256 assets) internal view returns (uint256) { uint256 _totalAssets = totalAssets(); - + // FIX C-02: Handle first deposit case - return assets directly for 1:1 ratio if (_totalAssets == 0 || totalShares == 0) { return assets; @@ -459,7 +427,7 @@ contract AttestifyVault is uint256 adjustedAssets = _totalAssets + VIRTUAL_ASSETS; uint256 adjustedShares = totalShares + VIRTUAL_SHARES; - + return (assets * adjustedShares) / adjustedAssets; } @@ -472,16 +440,16 @@ contract AttestifyVault is */ function _convertToAssets(uint256 _shares) internal view returns (uint256) { if (_shares == 0) return 0; - + // FIX C-02: On first deposit, return shares directly (1:1 ratio) // This matches _convertToShares behavior for consistency if (totalShares == 0) return _shares; - + uint256 adjustedShares = totalShares + VIRTUAL_SHARES; uint256 adjustedAssets = totalAssets() + VIRTUAL_ASSETS; - + if (adjustedAssets == 0) return 0; - + return (_shares * adjustedAssets) / adjustedShares; } @@ -612,7 +580,7 @@ contract AttestifyVault is } /* ========== STORAGE GAP ========== */ - + /** * @dev Storage gap for future upgrades * @dev Reduced by 1 to account for STRATEGY_GAS_LIMIT constant diff --git a/contracts/SelfProtocolVerifier.sol b/contracts/SelfProtocolVerifier.sol deleted file mode 100644 index 15c9b20..0000000 --- a/contracts/SelfProtocolVerifier.sol +++ /dev/null @@ -1,206 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {SelfVerificationRoot} from "@selfxyz/contracts/contracts/abstract/SelfVerificationRoot.sol"; -import {ISelfVerificationRoot} from "@selfxyz/contracts/contracts/interfaces/ISelfVerificationRoot.sol"; -import {SelfStructs} from "@selfxyz/contracts/contracts/libraries/SelfStructs.sol"; -import {SelfUtils} from "@selfxyz/contracts/contracts/libraries/SelfUtils.sol"; -import {IIdentityVerificationHubV2} from "@selfxyz/contracts/contracts/interfaces/IIdentityVerificationHubV2.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -/** - * @title SelfProtocolVerifier - * @notice Handles all Self Protocol verification logic - * @dev Separate contract allows updating verification without touching vault - */ -contract SelfProtocolVerifier is SelfVerificationRoot, Ownable { - - /* ========== STATE VARIABLES ========== */ - - // Verification configuration - address public hubV2; - SelfStructs.VerificationConfigV2 public verificationConfig; - bytes32 public verificationConfigId; - - // Authorized contracts (vaults) that can query verification status - mapping(address => bool) public authorizedCallers; - - // Verified users data - mapping(address => UserVerification) public verifications; - - struct UserVerification { - bool isVerified; - uint256 verifiedAt; - bytes32 nullifier; - uint256 age; // If disclosed - string nationality; // If disclosed - } - - /* ========== EVENTS ========== */ - - event UserVerified(address indexed user, bytes32 indexed nullifier, uint256 timestamp); - event CallerAuthorized(address indexed caller); - event CallerRevoked(address indexed caller); - event ConfigUpdated(bytes32 indexed newConfigId); - - /* ========== ERRORS ========== */ - - error Unauthorized(); - error AlreadyVerified(); - error NotVerified(); - - /* ========== CONSTRUCTOR ========== */ - - /** - * @notice Initialize Self verifier - * @param _hubV2 Self Protocol Hub V2 address - * @param _scopeSeed Unique scope identifier (max 31 bytes) - * @param _config Verification requirements - */ - constructor( - address _hubV2, - string memory _scopeSeed, - SelfUtils.UnformattedVerificationConfigV2 memory _config - ) - SelfVerificationRoot(_hubV2, _scopeSeed) - Ownable(msg.sender) - { - hubV2 = _hubV2; - verificationConfig = SelfUtils.formatVerificationConfigV2(_config); - verificationConfigId = IIdentityVerificationHubV2(_hubV2) - .setVerificationConfigV2(verificationConfig); - } - - /* ========== SELF PROTOCOL REQUIRED FUNCTIONS ========== */ - - /** - * @notice Return verification config ID for Self Hub - * @dev Called by Self Protocol during verification - */ - function getConfigId( - bytes32, // destinationChainId - bytes32, // userIdentifier - bytes memory // userDefinedData - ) public view override returns (bytes32) { - return verificationConfigId; - } - - /** - * @notice Called after successful verification by Self Hub - * @dev Store verification data for the user - */ - function customVerificationHook( - ISelfVerificationRoot.GenericDiscloseOutputV2 memory output, - bytes memory userData - ) internal override { - address user = msg.sender; - - // Prevent double verification - if (verifications[user].isVerified) revert AlreadyVerified(); - - // Store verification data - verifications[user] = UserVerification({ - isVerified: true, - verifiedAt: block.timestamp, - nullifier: bytes32(output.nullifier), - age: 0, // Extract from output if needed - nationality: "" // Extract from output if needed - }); - - emit UserVerified(user, bytes32(output.nullifier), block.timestamp); - } - - /* ========== PUBLIC VIEW FUNCTIONS ========== */ - - /** - * @notice Check if user is verified (callable by anyone) - * @param user Address to check - * @return True if verified - */ - function isVerified(address user) external view returns (bool) { - return verifications[user].isVerified; - } - - /** - * @notice Get user's nullifier (for sybil resistance) - * @param user Address to check - * @return Nullifier hash - */ - function getNullifier(address user) external view returns (bytes32) { - return verifications[user].nullifier; - } - - /** - * @notice Get full verification details (only authorized callers) - * @param user Address to check - * @return UserVerification struct - */ - function getVerificationDetails(address user) - external - view - returns (UserVerification memory) - { - if (!authorizedCallers[msg.sender]) revert Unauthorized(); - return verifications[user]; - } - - /* ========== ADMIN FUNCTIONS ========== */ - - /** - * @notice Authorize contract to query detailed verification data - * @param caller Contract address (e.g., vault) - */ - function authorizeCaller(address caller) external onlyOwner { - authorizedCallers[caller] = true; - emit CallerAuthorized(caller); - } - - /** - * @notice Revoke authorization - * @param caller Contract address - */ - function revokeCaller(address caller) external onlyOwner { - authorizedCallers[caller] = false; - emit CallerRevoked(caller); - } - - /** - * @notice Update verification config (creates new config ID) - * @param newConfig New verification requirements - */ - function updateConfig( - SelfUtils.UnformattedVerificationConfigV2 memory newConfig - ) external onlyOwner { - verificationConfig = SelfUtils.formatVerificationConfigV2(newConfig); - verificationConfigId = IIdentityVerificationHubV2(hubV2) - .setVerificationConfigV2(verificationConfig); - - emit ConfigUpdated(verificationConfigId); - } - - /** - * @notice Manual verification (emergency only) - * @param user User to verify - * @param nullifier Nullifier to assign - */ - function manualVerify(address user, bytes32 nullifier) external onlyOwner { - verifications[user] = UserVerification({ - isVerified: true, - verifiedAt: block.timestamp, - nullifier: nullifier, - age: 0, - nationality: "" - }); - - emit UserVerified(user, nullifier, block.timestamp); - } - - /** - * @notice Revoke verification (compliance/security) - * @param user User to revoke - */ - function revokeVerification(address user) external onlyOwner { - if (!verifications[user].isVerified) revert NotVerified(); - verifications[user].isVerified = false; - } -} \ No newline at end of file diff --git a/contracts/mocks/MockVerifier.sol b/contracts/mocks/MockVerifier.sol index 946f095..54b7de7 100644 --- a/contracts/mocks/MockVerifier.sol +++ b/contracts/mocks/MockVerifier.sol @@ -14,10 +14,9 @@ contract MockVerifier is ISelfVerifier { return _status[user]; } - function getNullifier(address) external pure override returns (bytes32) { + function getNullifier(address) external pure returns (bytes32) { return bytes32(0); } - function verifySelfProof(bytes memory, bytes memory) external pure override {} + function verifySelfProof(bytes memory, bytes memory) external pure {} } - diff --git a/scripts/deploy-vault.ts b/scripts/deploy-vault.ts index 1e50c6f..7f23954 100644 --- a/scripts/deploy-vault.ts +++ b/scripts/deploy-vault.ts @@ -71,7 +71,9 @@ async function loadConfig(chainId: bigint): Promise { asset: getAddress("ASSET_ADDRESS", defaults?.asset), aToken: getAddress("ATOKEN_ADDRESS", defaults?.aToken), addressesProvider: getAddress("AAVE_PROVIDER_ADDRESS", defaults?.addressesProvider), - verifier: requireAddress("VERIFIER_ADDRESS"), // Must be provided (deploy verifier first) + verifier: process.env.VERIFIER_ADDRESS + ? getAddress("VERIFIER_ADDRESS") + : ("0x0000000000000000000000000000000000000000" as Address), maxUserDeposit: getAmountEnv("MAX_USER_DEPOSIT", assetDecimals, "1000"), maxTotalDeposit: getAmountEnv("MAX_TOTAL_DEPOSIT", assetDecimals, "10000"), assetDecimals, @@ -98,43 +100,28 @@ console.log("- Max Total Deposit:", params.maxTotalDeposit.toString()); console.log(""); console.log("Deploying AaveV3Strategy..."); -// Deploy strategy with zero vault address (will be set after proxy deployment) const strategy = await viem.deployContract( "AaveV3Strategy", [params.asset, params.aToken, params.addressesProvider, "0x0000000000000000000000000000000000000000"], { account: deployer.account } ); +console.log("Strategy deployed at:", strategy.address); -console.log("Deploying AttestifyVault implementation..."); -const vaultImplementation = await viem.deployContract( +console.log("\nDeploying AttestifyVault (non-upgradeable, immutable)..."); +const vault = await viem.deployContract( "AttestifyVault", - [], - { account: deployer.account } -); - -const initData = encodeFunctionData({ - abi: vaultImplementation.abi, - functionName: "initialize", - args: [ + [ params.asset, strategy.address, params.verifier, params.maxUserDeposit, params.maxTotalDeposit, ], -}); - -console.log("Deploying ERC1967 proxy..."); -const proxy = await viem.deployContract( - "TestProxy", - [vaultImplementation.address, initData], { account: deployer.account } ); +console.log("Vault deployed at:", vault.address); -const vault = await viem.getContractAt("AttestifyVault", proxy.address); -console.log("Vault proxy deployed at:", vault.address); - -console.log("Linking strategy to vault..."); +console.log("\nLinking strategy to vault..."); await strategy.write.setVault([vault.address], { account: deployer.account, }); @@ -146,7 +133,7 @@ if (params.rebalancer && params.rebalancer !== deployer.account.address) { }); } -if (params.authorizeVaultOnVerifier) { +if (params.authorizeVaultOnVerifier && params.verifier !== "0x0000000000000000000000000000000000000000") { console.log("Authorizing vault on verifier..."); const verifier = await viem.getContractAt("SelfProtocolVerifier", params.verifier); await verifier.write.authorizeCaller([vault.address], { @@ -154,10 +141,10 @@ if (params.authorizeVaultOnVerifier) { }); } -console.log("\nDeployment complete:"); -console.log("- Strategy:", strategy.address); -console.log("- Vault Implementation:", vaultImplementation.address); -console.log("- Vault Proxy:", vault.address); +console.log("\nāœ… Deployment complete:"); +console.log("- Strategy: ", strategy.address); +console.log("- Vault (MAIN): ", vault.address); +console.log("\nšŸ”’ Contract is IMMUTABLE (not upgradeable)"); console.log("\nRemember to:"); console.log("1. Fund the vault with reserves if needed."); From e54817f1af511aab64375724385e8c842703fb6e Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Sat, 7 Feb 2026 15:56:42 +0100 Subject: [PATCH 2/3] smartcontract deployed --- .env | 2 +- .env.local | 3 +- .gitignore | 3 + DEPLOYMENT.MD | 0 contracts/AaveV3Strategy.sol | 36 +++---- contracts/AttestifyVault.sol | 147 +++----------------------- contracts/mocks/MockVerifier.sol | 22 ---- hardhat.config.ts | 6 +- package.json | 2 +- scripts/deploy-vault.ts | 149 ++++++++++---------------- scripts/deploy-verifier.ts | 127 +--------------------- scripts/test-deposit.js | 96 ++++++++--------- scripts/verify-contracts.ts | 142 +++++++++++++++---------- scripts/verify-sourcify.ts | 176 ------------------------------- 14 files changed, 227 insertions(+), 684 deletions(-) delete mode 100644 DEPLOYMENT.MD delete mode 100644 contracts/mocks/MockVerifier.sol delete mode 100644 scripts/verify-sourcify.ts diff --git a/.env b/.env index c49fd72..37eb90c 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -CELO_PRIVATE_KEY=0xc518ebb1d856a629d691457769c71b31a98ac833c1222e9adde8e4bec70b190f \ No newline at end of file +CELO_PRIVATE_KEY=0x87904685d468b4f8920b30df9bcc073122aa4064ce6ee0ca48224134fa61aea0 \ No newline at end of file diff --git a/.env.local b/.env.local index d368588..8e95ccf 100644 --- a/.env.local +++ b/.env.local @@ -1,5 +1,4 @@ -# āš ļø SECURITY: This private key is COMPROMISED - should be rotated -# For production, use a NEW private key + CELO_PRIVATE_KEY=0x87904685d468b4f8920b30df9bcc073122aa4064ce6ee0ca48224134fa61aea0 # Celo Mainnet Configuration diff --git a/.gitignore b/.gitignore index 0b1a1bc..e2c3a27 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ # Hardhat coverage reports /coverage + +# Deployment output (addresses) +/deployment-addresses.json diff --git a/DEPLOYMENT.MD b/DEPLOYMENT.MD deleted file mode 100644 index e69de29..0000000 diff --git a/contracts/AaveV3Strategy.sol b/contracts/AaveV3Strategy.sol index dbfd93b..ce6ccce 100644 --- a/contracts/AaveV3Strategy.sol +++ b/contracts/AaveV3Strategy.sol @@ -27,9 +27,10 @@ contract AaveV3Strategy is Ownable { IERC20 public immutable aToken; // acUSD token IPool public immutable aavePool; IPoolAddressesProvider public immutable addressesProvider; - - // Vault contract address (can be set after deployment if needed) - address public vault; + + // FIX C-01: Made vault immutable to prevent uninitialized state + // This ensures vault is always set at deployment and cannot be changed + address public immutable vault; /* ========== EVENTS ========== */ @@ -51,8 +52,8 @@ contract AaveV3Strategy is Ownable { * @param _asset Underlying asset (cUSD) * @param _aToken Aave interest-bearing token (acUSD) * @param _addressesProvider Aave PoolAddressesProvider - * @param _vault Vault contract address (can be zero, set later via setVault) - * @dev Vault can be updated after deployment via setVault() + * @param _vault Vault contract address (REQUIRED - cannot be zero) + * @dev FIX C-01: Vault is now required in constructor and immutable */ constructor( address _asset, @@ -60,11 +61,12 @@ contract AaveV3Strategy is Ownable { address _addressesProvider, address _vault ) Ownable(msg.sender) { - // Allow vault to be zero address initially (will be set later) + // FIX C-01: Added vault to zero address check if ( _asset == address(0) || _aToken == address(0) || - _addressesProvider == address(0) + _addressesProvider == address(0) || + _vault == address(0) ) { revert ZeroAddress(); } @@ -72,8 +74,8 @@ contract AaveV3Strategy is Ownable { asset = IERC20(_asset); aToken = IERC20(_aToken); addressesProvider = IPoolAddressesProvider(_addressesProvider); - - // Set vault (can be zero address, updated via setVault) + + // FIX C-01: Initialize immutable vault (no longer settable) vault = _vault; // Get Pool address from provider (recommended by Aave) @@ -94,16 +96,6 @@ contract AaveV3Strategy is Ownable { _; } - /** - * @notice Set or update the vault address - * @param _vault New vault address - * @dev Only callable by owner (deployer) - */ - function setVault(address _vault) external onlyOwner { - if (_vault == address(0)) revert ZeroAddress(); - vault = _vault; - } - /* ========== VAULT FUNCTIONS ========== */ /** @@ -120,7 +112,7 @@ contract AaveV3Strategy is Ownable { // FIX M-02: Check aToken balance before and after supply uint256 balanceBefore = aToken.balanceOf(address(this)); - + // Supply to Aave (receives aTokens automatically) aavePool.supply( address(asset), @@ -128,7 +120,7 @@ contract AaveV3Strategy is Ownable { address(this), 0 // No referral code ); - + uint256 balanceAfter = aToken.balanceOf(address(this)); uint256 received = balanceAfter - balanceBefore; @@ -275,4 +267,4 @@ contract AaveV3Strategy is Ownable { asset.safeTransfer(owner(), balance); } } -} +} \ No newline at end of file diff --git a/contracts/AttestifyVault.sol b/contracts/AttestifyVault.sol index a9ec45b..052b466 100644 --- a/contracts/AttestifyVault.sol +++ b/contracts/AttestifyVault.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/utils/Pausable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; @@ -12,27 +14,20 @@ import "./IAave.sol"; /** * @title AttestifyVault -<<<<<<< HEAD - * @notice Main vault contract for yield generation with identity verification - * @dev Non-upgradeable immutable vault, integrates with separate strategy contracts - * @dev AUDIT FIXES IMPLEMENTED: - * - C-02: Fixed share calculation for first deposit - * - H-01: Implemented gas-bounded circuit breaker pattern - * - M-01: Added front-running protection for rebalancing - * - L-01: Changed to basis points (1000 = 10%) for precision - * - I-01: Standardized all error handling with custom errors - * - I-02: Added comprehensive NatSpec documentation - * -======= * @notice Main vault contract for yield generation * @dev Upgradeable vault using UUPS pattern, integrates with separate strategy contracts * ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 * Architecture: * - Vault: Holds user funds, manages shares * - Strategy: Deploys funds to yield sources (Aave) */ -contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { +contract AttestifyVault is + Initializable, + UUPSUpgradeable, + OwnableUpgradeable, + ReentrancyGuardUpgradeable, + PausableUpgradeable +{ using SafeERC20 for IERC20; /* ========== STATE VARIABLES (DO NOT REORDER) ========== */ @@ -61,14 +56,7 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { uint256 public constant RESERVE_RATIO_BPS = 1000; // 10% kept in vault (1000 basis points = 10%) uint256 private constant VIRTUAL_SHARES = 1e3; // Virtual liquidity to harden share price uint256 private constant VIRTUAL_ASSETS = 1e3; -<<<<<<< HEAD - - // FIX H-01: Gas limit for strategy calls to prevent unbounded consumption - uint256 private constant STRATEGY_GAS_LIMIT = 100000; // 100k gas for external calls - -======= ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 // Admin address public treasury; address public rebalancer; // Can be AI agent @@ -99,9 +87,6 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { error StrategyDepositMismatch(uint256 expected, uint256 actual); /* ========== CONSTRUCTOR ========== */ -<<<<<<< HEAD - -======= /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -110,33 +95,18 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { /* ========== INITIALIZER ========== */ ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 /** - * @notice Initialize vault (immutable, cannot be changed) + * @notice Initialize vault (called once after proxy deployment) * @param _asset Underlying asset (cUSD) * @param _strategy Aave strategy contract -<<<<<<< HEAD - * @param _verifier Self verifier contract (optional, can be zero) - * @param _maxUserDeposit Max deposit per user - * @param _maxTotalDeposit Max total TVL - * @dev Non-upgradeable - contract code is permanent and cannot be changed -======= * @param _maxUserDeposit Max deposit per user * @param _maxTotalDeposit Max total TVL ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 */ - constructor( + function initialize( address _asset, address _strategy, uint256 _maxUserDeposit, uint256 _maxTotalDeposit -<<<<<<< HEAD - ) Ownable(msg.sender) { - if (_asset == address(0) || _strategy == address(0)) { - revert ZeroAddress(); - } - -======= ) external initializer { if (_asset == address(0) || _strategy == address(0)) { revert ZeroAddress(); @@ -147,7 +117,6 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { __ReentrancyGuard_init(); __Pausable_init(); ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 asset = IERC20(_asset); strategy = _strategy; maxUserDeposit = _maxUserDeposit; @@ -155,15 +124,6 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { treasury = msg.sender; rebalancer = msg.sender; } -<<<<<<< HEAD - - /** - * @notice Get contract version - * @return Version string - */ - function version() external pure returns (string memory) { - return "1.1.0"; // Non-upgradeable immutable version -======= /* ========== UPGRADE AUTHORIZATION ========== */ @@ -175,7 +135,6 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { function version() external pure returns (string memory) { return "1.0.0"; ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 } @@ -190,16 +149,6 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { * @return sharesIssued Shares minted to user * @dev Requires prior ERC20 approval. Use depositWithPermit() for gasless approval. */ -<<<<<<< HEAD - modifier onlyVerified() { - // If no verifier is configured, skip verification (integration removed/optional) - if (verifier != address(0)) { - if (!ISelfVerifier(verifier).isVerified(msg.sender)) { - revert NotVerified(); - } - } - _; -======= function deposit(uint256 assets) external nonReentrant @@ -228,7 +177,6 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { _deployToStrategy(assets); emit Deposited(msg.sender, assets, sharesIssued); ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 } /** @@ -411,31 +359,6 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { */ function totalAssets() public view returns (uint256) { uint256 reserveBalance = asset.balanceOf(address(this)); -<<<<<<< HEAD - uint256 strategyBalance = _getStrategyBalanceSafe(); - return reserveBalance + strategyBalance; - } - - /** - * @notice Safely get strategy balance with circuit breaker - * @return Strategy balance or 0 if call fails - * @dev FIX H-01: Implements gas-bounded circuit breaker pattern - * @dev Limits gas to prevent malicious strategy from consuming unbounded gas - */ - function _getStrategyBalanceSafe() internal view returns (uint256) { - if (strategy == address(0)) return 0; - - // FIX H-01: Use try-catch with gas limit to prevent unbounded consumption - // Circuit breaker: if strategy fails or exceeds gas, return 0 - try - IVaultYieldStrategy(strategy).totalAssets{gas: STRATEGY_GAS_LIMIT}() - returns (uint256 balance) { - return balance; - } catch { - // Strategy call failed - circuit breaker activates - // Return 0 for strategy balance, vault continues with reserve only - return 0; -======= uint256 strategyBalance = 0; if (strategy != address(0)) { @@ -444,7 +367,6 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { } catch { strategyBalance = 0; } ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 } return reserveBalance + strategyBalance; @@ -479,21 +401,8 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { * @notice Convert assets to shares */ function _convertToShares(uint256 assets) internal view returns (uint256) { -<<<<<<< HEAD - uint256 _totalAssets = totalAssets(); - - // FIX C-02: Handle first deposit case - return assets directly for 1:1 ratio - if (_totalAssets == 0 || totalShares == 0) { - return assets; - } - - uint256 adjustedAssets = _totalAssets + VIRTUAL_ASSETS; - uint256 adjustedShares = totalShares + VIRTUAL_SHARES; - -======= uint256 adjustedShares = totalShares + VIRTUAL_SHARES; uint256 adjustedAssets = totalAssets() + VIRTUAL_ASSETS; ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 return (assets * adjustedShares) / adjustedAssets; } @@ -501,23 +410,9 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { * @notice Convert shares to assets */ function _convertToAssets(uint256 _shares) internal view returns (uint256) { -<<<<<<< HEAD - if (_shares == 0) return 0; - - // FIX C-02: On first deposit, return shares directly (1:1 ratio) - // This matches _convertToShares behavior for consistency - if (totalShares == 0) return _shares; - - uint256 adjustedShares = totalShares + VIRTUAL_SHARES; - uint256 adjustedAssets = totalAssets() + VIRTUAL_ASSETS; - - if (adjustedAssets == 0) return 0; - -======= if (totalShares == 0) return 0; uint256 adjustedShares = totalShares + VIRTUAL_SHARES; uint256 adjustedAssets = totalAssets() + VIRTUAL_ASSETS; ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 return (_shares * adjustedAssets) / adjustedShares; } @@ -590,19 +485,7 @@ contract AttestifyVault is Ownable, ReentrancyGuard, Pausable { require(paused(), "Must be paused"); IERC20(token).safeTransfer(owner(), amount); } -<<<<<<< HEAD - - /* ========== STORAGE GAP ========== */ - - /** - * @dev Storage gap for future upgrades - * @dev Reduced by 1 to account for STRATEGY_GAS_LIMIT constant - */ - uint256[49] private __gap; -} -======= /* ========== STORAGE GAP ========== */ uint256[50] private __gap; -} ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 +} \ No newline at end of file diff --git a/contracts/mocks/MockVerifier.sol b/contracts/mocks/MockVerifier.sol deleted file mode 100644 index 54b7de7..0000000 --- a/contracts/mocks/MockVerifier.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "../IAave.sol"; - -contract MockVerifier is ISelfVerifier { - mapping(address => bool) private _status; - - function setVerified(address user, bool verified) external { - _status[user] = verified; - } - - function isVerified(address user) external view override returns (bool) { - return _status[user]; - } - - function getNullifier(address) external pure returns (bytes32) { - return bytes32(0); - } - - function verifySelfProof(bytes memory, bytes memory) external pure {} -} diff --git a/hardhat.config.ts b/hardhat.config.ts index 46322de..92cad92 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -76,7 +76,9 @@ export default defineConfig({ }, ], }, - sourcify: { - enabled: true, + verify: { + sourcify: { + enabled: true, + }, }, }); diff --git a/package.json b/package.json index 2840632..9f7f18d 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "test": "hardhat test", - "deploy": "hardhat run scripts/deploy-vault.ts --network celoMainnet", + "deploy": "hardhat compile --build-profile production && hardhat run scripts/deploy-vault.ts --network celoMainnet", "deploy:verifier": "hardhat run scripts/deploy-verifier.ts --network celoMainnet", "verify": "hardhat run scripts/verify-contracts.ts --network celoMainnet", "query-aave": "hardhat run scripts/query-aave-addresses.ts" diff --git a/scripts/deploy-vault.ts b/scripts/deploy-vault.ts index 3a113f4..908fa79 100644 --- a/scripts/deploy-vault.ts +++ b/scripts/deploy-vault.ts @@ -1,5 +1,7 @@ import "dotenv/config"; import process from "node:process"; +import { writeFileSync } from "node:fs"; +import { join } from "node:path"; import { network } from "hardhat"; import { encodeFunctionData, parseUnits, createWalletClient, http } from "viem"; @@ -53,15 +55,15 @@ function getAmountEnv(name: string, decimals: number, fallback: string): bigint async function loadConfig(chainId: bigint): Promise { const assetDecimals = Number(process.env.ASSET_DECIMALS ?? "18"); - + // Get defaults based on chain // Convert to number for comparison to handle both bigint and number const chainIdNum = Number(chainId); const defaults = chainIdNum === 42220 - ? CELO_MAINNET_DEFAULTS + ? CELO_MAINNET_DEFAULTS : chainIdNum === 44787 - ? CELO_ALFAJORES_DEFAULTS - : null; + ? CELO_ALFAJORES_DEFAULTS + : null; if (!defaults) { console.warn(`āš ļø Unknown chain (chainId: ${chainIdNum}), using env vars only (no defaults)`); @@ -71,12 +73,6 @@ async function loadConfig(chainId: bigint): Promise { asset: getAddress("ASSET_ADDRESS", defaults?.asset), aToken: getAddress("ATOKEN_ADDRESS", defaults?.aToken), addressesProvider: getAddress("AAVE_PROVIDER_ADDRESS", defaults?.addressesProvider), -<<<<<<< HEAD - verifier: process.env.VERIFIER_ADDRESS - ? getAddress("VERIFIER_ADDRESS") - : ("0x0000000000000000000000000000000000000000" as Address), -======= ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 maxUserDeposit: getAmountEnv("MAX_USER_DEPOSIT", assetDecimals, "1000"), maxTotalDeposit: getAmountEnv("MAX_TOTAL_DEPOSIT", assetDecimals, "10000"), assetDecimals, @@ -86,9 +82,9 @@ async function loadConfig(chainId: bigint): Promise { // Determine chainId based on network name (before connecting) // This avoids RPC calls that might timeout -const networkName = process.argv.find(arg => arg.includes("--network"))?.split("=")[1] || - process.argv[process.argv.indexOf("--network") + 1] || - "celoMainnet"; +const networkName = process.argv.find(arg => arg.includes("--network"))?.split("=")[1] || + process.argv[process.argv.indexOf("--network") + 1] || + "celoMainnet"; let chainId: bigint; if (networkName === "celoMainnet" || networkName.includes("mainnet")) { @@ -135,124 +131,85 @@ console.log(""); // Get contract artifacts from Hardhat const hre = await import("hardhat"); + const strategyArtifact = await hre.artifacts.readArtifact("AaveV3Strategy"); const vaultArtifact = await hre.artifacts.readArtifact("AttestifyVault"); -const proxyArtifact = await hre.artifacts.readArtifact("TestProxy"); -console.log("Deploying AaveV3Strategy..."); -<<<<<<< HEAD -const strategy = await viem.deployContract( - "AaveV3Strategy", - [params.asset, params.aToken, params.addressesProvider, "0x0000000000000000000000000000000000000000"], - { account: deployer.account } -); -console.log("Strategy deployed at:", strategy.address); -console.log("\nDeploying AttestifyVault (non-upgradeable, immutable)..."); -const vault = await viem.deployContract( - "AttestifyVault", - [ -======= + +console.log("Deploying AttestifyVault..."); +const vaultHash = await deployer.deployContract({ + abi: vaultArtifact.abi, + bytecode: vaultArtifact.bytecode as `0x${string}`, + // No constructor args for upgradeable contract + gas: 8000000n, +}); +const vaultReceipt = await publicClient.waitForTransactionReceipt({ hash: vaultHash }); +const vaultAddress = vaultReceipt.contractAddress!; +console.log("Vault deployed at:", vaultAddress); + +// Deploy AaveV3Strategy first, since initialize needs its address +console.log("Deploying AaveV3Strategy..."); const strategyHash = await deployer.deployContract({ abi: strategyArtifact.abi, bytecode: strategyArtifact.bytecode as `0x${string}`, - args: [params.asset, params.aToken, params.addressesProvider], + args: [params.asset, params.aToken, params.addressesProvider, vaultAddress], + gas: 8000000n, }); const strategyReceipt = await publicClient.waitForTransactionReceipt({ hash: strategyHash }); const strategyAddress = strategyReceipt.contractAddress!; console.log("Strategy deployed at:", strategyAddress); -console.log("Deploying AttestifyVault implementation..."); -const vaultHash = await deployer.deployContract({ - abi: vaultArtifact.abi, - bytecode: vaultArtifact.bytecode as `0x${string}`, -}); -const vaultReceipt = await publicClient.waitForTransactionReceipt({ hash: vaultHash }); -const vaultImplementationAddress = vaultReceipt.contractAddress!; -console.log("Vault implementation deployed at:", vaultImplementationAddress); - -// Encode init data using artifact ABI -const initData = encodeFunctionData({ +// Call initialize on the vault +console.log("Initializing AttestifyVault..."); +await deployer.writeContract({ + address: vaultAddress, abi: vaultArtifact.abi, functionName: "initialize", args: [ ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 params.asset, strategyAddress, params.maxUserDeposit, params.maxTotalDeposit, ], -<<<<<<< HEAD - { account: deployer.account } -); -console.log("Vault deployed at:", vault.address); - -console.log("\nLinking strategy to vault..."); -await strategy.write.setVault([vault.address], { - account: deployer.account, -======= + gas: 2000000n, }); +console.log("Vault initialized."); -console.log("Deploying ERC1967 proxy..."); -const proxyHash = await deployer.deployContract({ - abi: proxyArtifact.abi, - bytecode: proxyArtifact.bytecode as `0x${string}`, - args: [vaultImplementationAddress, initData], -}); -const proxyReceipt = await publicClient.waitForTransactionReceipt({ hash: proxyHash }); -const vaultAddress = proxyReceipt.contractAddress!; - -console.log("Vault proxy deployed at:", vaultAddress); - -// Wait for proxy deployment to be confirmed before next transaction -await publicClient.waitForTransactionReceipt({ hash: proxyHash }); - -console.log("Linking strategy to vault..."); -await deployer.writeContract({ - address: strategyAddress, - abi: strategyArtifact.abi, - functionName: "setVault", - args: [vaultAddress], ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 -}); if (params.rebalancer && params.rebalancer !== account.address) { console.log("Setting custom rebalancer:", params.rebalancer); -<<<<<<< HEAD - await vault.write.setRebalancer([params.rebalancer], { - account: deployer.account, - }); -} - -if (params.authorizeVaultOnVerifier && params.verifier !== "0x0000000000000000000000000000000000000000") { - console.log("Authorizing vault on verifier..."); - const verifier = await viem.getContractAt("SelfProtocolVerifier", params.verifier); - await verifier.write.authorizeCaller([vault.address], { - account: deployer.account, - }); -} - -console.log("\nāœ… Deployment complete:"); -console.log("- Strategy: ", strategy.address); -console.log("- Vault (MAIN): ", vault.address); -console.log("\nšŸ”’ Contract is IMMUTABLE (not upgradeable)"); -======= await deployer.writeContract({ address: vaultAddress, abi: vaultArtifact.abi, functionName: "setRebalancer", args: [params.rebalancer], + gas: 500000n, }); } +// Save deployment addresses for verification +const deploymentPath = join(process.cwd(), "deployment-addresses.json"); +const deployment = { + network: networkName, + chainId: chainId.toString(), + vault: vaultAddress, + strategy: strategyAddress, + strategyConstructorArgs: [ + params.asset, + params.aToken, + params.addressesProvider, + vaultAddress, + ], +}; +writeFileSync(deploymentPath, JSON.stringify(deployment, null, 2)); +console.log("\nDeployment saved to:", deploymentPath); + console.log("\nDeployment complete:"); +console.log("- Vault:", vaultAddress); console.log("- Strategy:", strategyAddress); -console.log("- Vault Implementation:", vaultImplementationAddress); -console.log("- Vault Proxy:", vaultAddress); ->>>>>>> 84daae5f7f7783b2770ec42a45dca5bd6568daa7 console.log("\nšŸ“ Next steps:"); -console.log("1. Verify contracts: npx hardhat run scripts/verify-contracts.ts --network celoMainnet"); +console.log("1. Verify on Sourcify: npx hardhat run scripts/verify-contracts.ts --network celoMainnet"); console.log("2. Fund the vault with reserves if needed."); -console.log("3. Configure rebalancer/treasury roles and deposit limits as required."); - +console.log("3. Configure rebalancer/treasury roles and deposit limits as required."); \ No newline at end of file diff --git a/scripts/deploy-verifier.ts b/scripts/deploy-verifier.ts index 85f5b76..0d5390c 100644 --- a/scripts/deploy-verifier.ts +++ b/scripts/deploy-verifier.ts @@ -7,129 +7,4 @@ import { encodeAbiParameters, encodeFunctionData } from "viem"; import { network } from "hardhat"; import type { Address } from "viem"; -// Celo Mainnet addresses (from query) -const CELO_MAINNET_ADDRESSES = { - selfHubV2: "0xe57F4773bd9c9d8b6Cd70431117d353298B9f5BF" as Address, - cUSD: "0x765DE816845861e75A25fCA122bb6898B8B1282a" as Address, -} as const; - -// Celo Alfajores Testnet addresses -const CELO_ALFAJORES_ADDRESSES = { - selfHubV2: "0x18E05eAC6F31d03fb188FDc8e72FF354aB24EaB6" as Address, - cUSD: "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1" as Address, -} as const; - -function getAddresses(chainId: bigint | number) { - const id = typeof chainId === "bigint" ? chainId : BigInt(chainId); - if (id === 42220n) { - return CELO_MAINNET_ADDRESSES; - } else if (id === 44787n) { - return CELO_ALFAJORES_ADDRESSES; - } - throw new Error(`Unsupported chain ID: ${chainId}`); -} - -async function deployVerifier() { - const { viem } = await network.connect(); - const publicClient = await viem.getPublicClient(); - const [deployer] = await viem.getWalletClients(); - const chainId = await publicClient.getChainId(); - - console.log("Deploying SelfProtocolVerifier..."); - console.log("Deployer:", deployer.account.address); - console.log("Chain ID:", chainId.toString()); - - const addresses = getAddresses(chainId); - const hubV2 = process.env.SELF_HUB_V2 as Address | undefined || addresses.selfHubV2; - const scopeSeed = process.env.SELF_SCOPE_SEED || "attestify-v1"; - - // Verification config from env or use defaults - const minimumAge = Number(process.env.SELF_MIN_AGE || "18"); - const ofacEnabled = process.env.SELF_OFAC_ENABLED === "true"; - - // Parse forbidden countries from env (comma-separated) or use empty array - const forbiddenCountriesStr = process.env.SELF_FORBIDDEN_COUNTRIES || ""; - const forbiddenCountries = forbiddenCountriesStr - ? forbiddenCountriesStr.split(",").map((c) => c.trim()).filter(Boolean) - : []; - - console.log("\nConfiguration:"); - console.log("- Self Hub V2:", hubV2); - console.log("- Scope Seed:", scopeSeed); - console.log("- Minimum Age:", minimumAge); - console.log("- OFAC Enabled:", ofacEnabled); - console.log("- Forbidden Countries:", forbiddenCountries.length > 0 ? forbiddenCountries.join(", ") : "None"); - - // Build the verification config struct - // Note: SelfUtils.UnformattedVerificationConfigV2 expects: - // - olderThan: uint256 - // - forbiddenCountries: string[] - // - ofacEnabled: bool - const config = { - olderThan: BigInt(minimumAge), - forbiddenCountries: forbiddenCountries, - ofacEnabled: ofacEnabled, - }; - - console.log("\nDeploying SelfProtocolVerifier contract..."); - console.log("Config:", JSON.stringify(config, (_, v) => typeof v === 'bigint' ? v.toString() : v)); - - // Format config as explicit tuple for ABI encoding - const configTuple: [bigint, string[], boolean] = [ - config.olderThan, - config.forbiddenCountries, - config.ofacEnabled, - ]; - - // Read artifact directly - const artifactPath = join(process.cwd(), "artifacts", "contracts", "SelfProtocolVerifier.sol", "SelfProtocolVerifier.json"); - const artifact = JSON.parse(readFileSync(artifactPath, "utf-8")); - - // Encode constructor parameters - const constructorAbi = artifact.abi.find((item: any) => item.type === "constructor"); - if (!constructorAbi) { - throw new Error("Constructor not found in ABI"); - } - - const encodedArgs = encodeAbiParameters( - constructorAbi.inputs, - [hubV2, scopeSeed, configTuple] - ); - - // Deploy contract - const hash = await deployer.sendTransaction({ - data: (artifact.bytecode as string) + encodedArgs.slice(2), // Remove 0x prefix from encoded args - }); - - console.log("Transaction hash:", hash); - console.log("Waiting for deployment confirmation..."); - - // Wait for deployment - const receipt = await publicClient.waitForTransactionReceipt({ hash }); - - // Get deployed address from receipt - const verifierAddress = receipt.contractAddress; - - if (!verifierAddress) { - throw new Error("Contract deployment failed - no contract address in receipt"); - } - - console.log("Contract deployed at:", verifierAddress); - - const verifier = await viem.getContractAt("SelfProtocolVerifier", verifierAddress); - - console.log("\nāœ… SelfProtocolVerifier deployed!"); - console.log("Address:", verifier.address); - - console.log("\n=== For Vault Deployment ==="); - console.log("Set in your .env file:"); - console.log(`VERIFIER_ADDRESS=${verifier.address}`); - - return verifier.address; -} - -deployVerifier().catch((error) => { - console.error("Error deploying verifier:", error); - process.exit(1); -}); - +// Self protocol integration removed. Add your new verifier deployment logic here if needed. \ No newline at end of file diff --git a/scripts/test-deposit.js b/scripts/test-deposit.js index 676f59d..4a98e28 100644 --- a/scripts/test-deposit.js +++ b/scripts/test-deposit.js @@ -1,56 +1,50 @@ const { ethers } = require("hardhat"); +/** + * Check vault state before depositing. + * Current AttestifyVault has no on-chain verifier — anyone can deposit (no human verification gate). + */ async function main() { - const [signer] = await ethers.getSigners(); - - const vaultAddress = "0xbf277f1e43d825a481fe807ab145f812a34233e6"; - const cusdAddress = "0x765DE816845861e75A25fCA122bb6898B8B1282a"; - - const vault = await ethers.getContractAt("AttestifyVault", vaultAddress); - const cusd = await ethers.getContractAt("IERC20", cusdAddress); - - console.log("Checking vault state..."); - - // Check if initialized - const owner = await vault.owner(); - console.log("Owner:", owner); - console.log("Initialized:", owner !== ethers.constants.AddressZero); - - // Check paused - const paused = await vault.paused(); - console.log("Paused:", paused); - - // Check balance - const balance = await cusd.balanceOf(signer.address); - console.log("Your cUSD balance:", ethers.utils.formatEther(balance)); - - // Check allowance - const allowance = await cusd.allowance(signer.address, vaultAddress); - console.log("Vault allowance:", ethers.utils.formatEther(allowance)); - - // Check limits - const maxUser = await vault.maxUserDeposit(); - const maxTotal = await vault.maxTotalDeposit(); - const totalDeposits = await vault.totalDeposits(); - const userDeposit = await vault.getUserDeposit(signer.address); - - console.log("Max per user:", ethers.utils.formatEther(maxUser)); - console.log("Max total:", ethers.utils.formatEther(maxTotal)); - console.log("Current total:", ethers.utils.formatEther(totalDeposits)); - console.log("Your deposits:", ethers.utils.formatEther(userDeposit)); - - // Check verification - const verifierAddress = await vault.verifier(); - const verifier = await ethers.getContractAt("SelfProtocolVerifier", verifierAddress); - const isVerified = await verifier.isVerified(signer.address); - console.log("Self Protocol verified:", isVerified); - - console.log("\n=== Ready to deposit? ==="); - console.log("āœ… Initialized:", owner !== ethers.constants.AddressZero); - console.log("āœ… Not paused:", !paused); - console.log("āœ… Has cUSD:", balance.gt(0)); - console.log("āœ… Approved:", allowance.gt(0)); - console.log("āœ… Verified:", isVerified); + const [signer] = await ethers.getSigners(); + + // Update to your vault address (or use deployment-addresses.json) + const vaultAddress = process.env.VAULT_ADDRESS || "0x154e0a62d5d25bb405a6395ef8da0fdf33c6284a"; + const cusdAddress = "0x765DE816845861e75A25fCA122bb6898B8B1282a"; + + const vault = await ethers.getContractAt("AttestifyVault", vaultAddress); + const cusd = await ethers.getContractAt("IERC20", cusdAddress); + + console.log("Checking vault state..."); + + const owner = await vault.owner(); + console.log("Owner:", owner); + console.log("Initialized:", owner !== ethers.constants.AddressZero); + + const paused = await vault.paused(); + console.log("Paused:", paused); + + const balance = await cusd.balanceOf(signer.address); + console.log("Your cUSD balance:", ethers.utils.formatEther(balance)); + + const allowance = await cusd.allowance(signer.address, vaultAddress); + console.log("Vault allowance:", ethers.utils.formatEther(allowance)); + + const maxUser = await vault.maxUserDeposit(); + const maxTotal = await vault.maxTotalDeposit(); + const totalAssets = await vault.totalAssets(); + const userBalance = await vault.balanceOf(signer.address); + + console.log("Max per user:", ethers.utils.formatEther(maxUser)); + console.log("Max total:", ethers.utils.formatEther(maxTotal)); + console.log("Total assets (TVL):", ethers.utils.formatEther(totalAssets)); + console.log("Your vault balance:", ethers.utils.formatEther(userBalance)); + + console.log("\n=== Ready to deposit? ==="); + console.log("āœ… Initialized:", owner !== ethers.constants.AddressZero); + console.log("āœ… Not paused:", !paused); + console.log("āœ… Has cUSD:", balance.gt(0)); + console.log("āœ… Approved:", allowance.gt(0)); + console.log("(No on-chain human verification — vault does not gate deposits by verifier)"); } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/scripts/verify-contracts.ts b/scripts/verify-contracts.ts index 4049c4e..e02c3ef 100644 --- a/scripts/verify-contracts.ts +++ b/scripts/verify-contracts.ts @@ -1,75 +1,111 @@ import "dotenv/config"; -import { network } from "hardhat"; +import process from "node:process"; +import { readFileSync, existsSync } from "node:fs"; +import { join } from "node:path"; -// Contract addresses from deployment -const CONTRACTS = { - strategy: "0x1ed36feb312b9d464d95fc1bab4b286ddc793341", - vaultImplementation: "0xbe70318eb8772d265642a2ab6fee32cd250ec844", - vaultProxy: "0x16a0ff8d36d9d660de8fd5257cff78adf11b8306", -}; - -// Constructor arguments for Strategy -const STRATEGY_ARGS = [ - "0x765DE816845861e75A25fCA122bb6898B8B1282a", // asset (cUSD) - "0xBba98352628B0B0c4b40583F593fFCb630935a45", // aToken (acUSD) - "0x9F7Cf9417D5251C59fE94fB9147feEe1aAd9Cea5", // addressesProvider -]; +import { verifyContract } from "@nomicfoundation/hardhat-verify/verify"; +/** + * Verify deployed contracts on Sourcify (Celo Mainnet). + * + * Addresses are read from: + * 1. deployment-addresses.json (created by deploy-vault.ts) + * 2. Or env vars: VAULT_ADDRESS, STRATEGY_ADDRESS, STRATEGY_CONSTRUCTOR_ARGS (JSON array) + * 3. Or CLI args: --vault 0x... --strategy 0x... + */ async function main() { - console.log("šŸ” Starting contract verification on Sourcify...\n"); - console.log("Network: Celo Mainnet (42220)\n"); + const chainId = 42220; + + // Resolve addresses + let vaultAddress: string; + let strategyAddress: string; + let strategyConstructorArgs: string[]; + + const deploymentPath = join(process.cwd(), "deployment-addresses.json"); + if (existsSync(deploymentPath)) { + const deployment = JSON.parse(readFileSync(deploymentPath, "utf-8")); + vaultAddress = deployment.vault; + strategyAddress = deployment.strategy; + strategyConstructorArgs = deployment.strategyConstructorArgs ?? []; + console.log("šŸ“‚ Using addresses from deployment-addresses.json\n"); + } else { + const vaultArg = process.argv.find((a) => a.startsWith("--vault="))?.split("=")[1]; + const strategyArg = process.argv.find((a) => a.startsWith("--strategy="))?.split("=")[1]; + vaultAddress = process.env.VAULT_ADDRESS ?? vaultArg ?? ""; + strategyAddress = process.env.STRATEGY_ADDRESS ?? strategyArg ?? ""; + + if (process.env.STRATEGY_CONSTRUCTOR_ARGS) { + strategyConstructorArgs = JSON.parse(process.env.STRATEGY_CONSTRUCTOR_ARGS); + } else if (!vaultAddress || !strategyAddress) { + throw new Error( + "No deployment-addresses.json found. Provide addresses via:\n" + + " - Run deploy-vault.ts first (creates deployment-addresses.json)\n" + + " - Env: VAULT_ADDRESS, STRATEGY_ADDRESS, STRATEGY_CONSTRUCTOR_ARGS\n" + + " - CLI: --vault=0x... --strategy=0x..." + ); + } else { + // Celo mainnet defaults for strategy constructor + strategyConstructorArgs = [ + "0x765DE816845861e75A25fCA122bb6898B8B1282a", // asset (cUSD) + "0xBba98352628B0B0c4b40583F593fFCb630935a45", // aToken (acUSD) + "0x9F7Cf9417D5251C59fE94fB9147feEe1aAd9Cea5", // addressesProvider + vaultAddress, // vault + ]; + } + } + + console.log("šŸ” Verifying contracts on Sourcify (Celo Mainnet - 42220)\n"); - // Import hardhat to get the run function const hre = await import("hardhat"); - // Verify Strategy + // Verify AaveV3Strategy console.log("1ļøāƒ£ Verifying AaveV3Strategy..."); try { - await hre.default.run("verify:verify", { - address: CONTRACTS.strategy, - constructorArguments: STRATEGY_ARGS, - contract: "contracts/AaveV3Strategy.sol:AaveV3Strategy", - }); - console.log("āœ… AaveV3Strategy verified successfully!\n"); - } catch (error: any) { - const errorMsg = error.message || String(error); - if (errorMsg.includes("Already Verified") || errorMsg.includes("already verified")) { + await verifyContract( + { + address: strategyAddress, + constructorArgs: strategyConstructorArgs, + contract: "contracts/AaveV3Strategy.sol:AaveV3Strategy", + provider: "sourcify", + }, + hre.default + ); + console.log("āœ… AaveV3Strategy verified!\n"); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + if (msg.includes("Already Verified") || msg.includes("already verified") || msg.includes("already verified")) { console.log("āœ… AaveV3Strategy already verified!\n"); } else { - console.error("āŒ Error verifying AaveV3Strategy:"); - console.error(errorMsg); - console.log(""); + console.error("āŒ AaveV3Strategy verification failed:", msg, "\n"); } } - // Verify Vault Implementation - console.log("2ļøāƒ£ Verifying AttestifyVault implementation..."); + // Verify AttestifyVault (implementation, no constructor args) + console.log("2ļøāƒ£ Verifying AttestifyVault..."); try { - await hre.default.run("verify:verify", { - address: CONTRACTS.vaultImplementation, - constructorArguments: [], // Implementation has no constructor args (upgradeable) - contract: "contracts/AttestifyVault.sol:AttestifyVault", - }); - console.log("āœ… AttestifyVault implementation verified successfully!\n"); - } catch (error: any) { - const errorMsg = error.message || String(error); - if (errorMsg.includes("Already Verified") || errorMsg.includes("already verified")) { - console.log("āœ… AttestifyVault implementation already verified!\n"); + await verifyContract( + { + address: vaultAddress, + constructorArgs: [], + contract: "contracts/AttestifyVault.sol:AttestifyVault", + provider: "sourcify", + }, + hre.default + ); + console.log("āœ… AttestifyVault verified!\n"); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + if (msg.includes("Already Verified") || msg.includes("already verified")) { + console.log("āœ… AttestifyVault already verified!\n"); } else { - console.error("āŒ Error verifying AttestifyVault:"); - console.error(errorMsg); - console.log(""); + console.error("āŒ AttestifyVault verification failed:", msg, "\n"); } } - // Note: Proxy contracts typically don't need verification as they're standard implementations - console.log("šŸ“ Note: Proxy contract verification skipped (standard ERC1967 implementation)"); - - console.log("\nšŸŽ‰ Verification process complete!"); - console.log("\nšŸ“‹ View verified contracts at:"); - console.log(` Strategy: https://sourcify.dev/#/contracts/full_match/42220/${CONTRACTS.strategy}`); - console.log(` Vault: https://sourcify.dev/#/contracts/full_match/42220/${CONTRACTS.vaultImplementation}`); - console.log(`\n Or check: https://repo.sourcify.dev/contracts/full_match/42220/`); + console.log("šŸŽ‰ Verification complete!"); + console.log("\nšŸ“‹ View verified contracts:"); + console.log(` Strategy: https://sourcify.dev/#/contracts/full_match/${chainId}/${strategyAddress}`); + console.log(` Vault: https://sourcify.dev/#/contracts/full_match/${chainId}/${vaultAddress}`); } main().catch((error) => { diff --git a/scripts/verify-sourcify.ts b/scripts/verify-sourcify.ts deleted file mode 100644 index 60ff7fb..0000000 --- a/scripts/verify-sourcify.ts +++ /dev/null @@ -1,176 +0,0 @@ -import "dotenv/config"; -import { readFileSync } from "fs"; -import { join, dirname } from "path"; -import { fileURLToPath } from "url"; -import process from "node:process"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -// Contract addresses from deployment -const CONTRACTS = { - strategy: "0x1ed36feb312b9d464d95fc1bab4b286ddc793341", - vaultImplementation: "0xbe70318eb8772d265642a2ab6fee32cd250ec844", - vaultProxy: "0x16a0ff8d36d9d660de8fd5257cff78adf11b8306", -}; - -const CHAIN_ID = 42220; // Celo Mainnet -const SOURCIFY_API = "https://sourcify.dev/server"; - -interface ContractFiles { - [key: string]: { - content: string; - }; -} - -async function verifyContract( - address: string, - contractName: string, - files: ContractFiles -) { - console.log(`\nšŸ” Verifying ${contractName} at ${address}...`); - - // Sourcify expects files in a specific format - const requestBody = { - address, - chain: CHAIN_ID.toString(), - files: files, - }; - - try { - const response = await fetch(`${SOURCIFY_API}/verify`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(requestBody), - }); - - if (!response.ok) { - const errorText = await response.text(); - console.log(`āŒ HTTP Error: ${response.status} - ${errorText}`); - return false; - } - - const result = await response.json(); - - if (result.status === "perfect" || result.status === "partial") { - console.log(`āœ… ${contractName} verified successfully!`); - console.log(` Status: ${result.status}`); - console.log(` View at: https://sourcify.dev/#/contracts/${result.status}/${CHAIN_ID}/${address}`); - return true; - } else if (result.error) { - console.log(`āŒ ${contractName} verification failed`); - console.log(` Error: ${result.error}`); - if (result.message) { - console.log(` Message: ${result.message}`); - } - return false; - } else { - console.log(`āŒ ${contractName} verification failed`); - console.log(` Response: ${JSON.stringify(result, null, 2)}`); - return false; - } - } catch (error: any) { - console.error(`āŒ Error verifying ${contractName}:`, error.message || error); - return false; - } -} - -function getContractFiles(contractName: string): { files: ContractFiles; metadata?: any } { - const files: ContractFiles = {}; - - // Read main contract - const contractPath = join(__dirname, "../contracts", `${contractName}.sol`); - try { - files[`contracts/${contractName}.sol`] = { - content: readFileSync(contractPath, "utf-8"), - }; - } catch (error) { - console.error(`Error reading ${contractName}.sol:`, error); - } - - // Read dependencies - const dependencies: string[] = []; - - if (contractName === "AaveV3Strategy") { - // AaveV3Strategy doesn't directly import IAave, but let's include it if needed - } else if (contractName === "AttestifyVault") { - dependencies.push("IAave"); - } - - // Read dependency files - for (const dep of dependencies) { - const depPath = join(__dirname, "../contracts", `${dep}.sol`); - try { - files[`contracts/${dep}.sol`] = { - content: readFileSync(depPath, "utf-8"), - }; - } catch (error) { - console.warn(`Warning: Could not read ${dep}.sol`); - } - } - - // Try to read metadata.json from artifacts - let metadata; - try { - const metadataPath = join(__dirname, "../artifacts/contracts", `${contractName}.sol`, `${contractName}.json`); - const artifact = JSON.parse(readFileSync(metadataPath, "utf-8")); - if (artifact.metadata) { - metadata = JSON.parse(artifact.metadata); - files["metadata.json"] = { - content: artifact.metadata, - }; - } - } catch (error) { - console.warn(`Warning: Could not read metadata for ${contractName}`); - } - - return { files, metadata }; -} - -async function main() { - console.log("šŸš€ Starting Sourcify verification for Celo Mainnet contracts...\n"); - - const results = { - strategy: false, - vaultImplementation: false, - vaultProxy: false, - }; - - // Verify Strategy - const strategyData = getContractFiles("AaveV3Strategy"); - results.strategy = await verifyContract( - CONTRACTS.strategy, - "AaveV3Strategy", - strategyData.files - ); - - // Verify Vault Implementation - const vaultData = getContractFiles("AttestifyVault"); - results.vaultImplementation = await verifyContract( - CONTRACTS.vaultImplementation, - "AttestifyVault", - vaultData.files - ); - - // Note: Proxy contracts (TestProxy) are typically not verified through Sourcify - // as they're standard proxy implementations - console.log("\nšŸ“ Note: Proxy contracts are typically not verified separately."); - console.log(" The proxy uses a standard ERC1967 implementation."); - - console.log("\nšŸ“Š Verification Summary:"); - console.log(` Strategy: ${results.strategy ? "āœ… Verified" : "āŒ Failed"}`); - console.log(` Vault Implementation: ${results.vaultImplementation ? "āœ… Verified" : "āŒ Failed"}`); - - if (results.strategy && results.vaultImplementation) { - console.log("\nšŸŽ‰ All contracts verified successfully!"); - } else { - console.log("\nāš ļø Some contracts failed verification. Check the errors above."); - } -} - -main().catch((error) => { - console.error("Error:", error); - process.exit(1); -}); From 04a884b51f9be6f7384e9555a265c147f63f1a93 Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Sat, 7 Feb 2026 16:06:56 +0100 Subject: [PATCH 3/3] env fixed --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e2c3a27..acf1b4a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,12 @@ #dotenv .env +.env.local # Compilation output /dist + # pnpm deploy output /bundle