Contract Security
How KhipuVault smart contracts are secured with multi-sig, time-locks, and security best practices.
Contract Security
KhipuVault smart contracts are designed with security as the top priority. This page explains how our contracts protect your Bitcoin.
Security Architecture
Non-Custodial Design
You Always Control Your Funds
KhipuVault contracts are non-custodial. You retain full ownership and can withdraw your funds at any time without our permission.
Key Principles:
✅ No admin withdrawal - Only you can withdraw your funds ✅ No fund freezing - Your deposits are always accessible ✅ Transparent logic - All code is open-source and verified ✅ Emergency exits - Withdraw even if our website is down
Implementation:
// Example from IndividualPool.sol
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient balance");
// User controls their own withdrawal
balances[msg.sender] -= amount;
// Transfer directly to user
IERC20(musdToken).safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount, block.timestamp);
}No admin can stop or redirect this withdrawal.
Smart Contract Security Patterns
1. Reentrancy Protection
The Risk: Malicious contracts can call back into our contracts before state updates complete, potentially draining funds.
Our Protection:
We use OpenZeppelin's ReentrancyGuard on all state-changing functions.
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract IndividualPool is ReentrancyGuard {
function deposit(uint256 amount) external nonReentrant {
// Protected against reentrancy
_processDeposit(msg.sender, amount);
}
function withdraw(uint256 amount) external nonReentrant {
// Protected against reentrancy
_processWithdrawal(msg.sender, amount);
}
}Additional Safety: We follow the Checks-Effects-Interactions pattern:
function withdraw(uint256 amount) external nonReentrant {
// 1. CHECKS
require(balances[msg.sender] >= amount, "Insufficient balance");
// 2. EFFECTS (state changes)
balances[msg.sender] -= amount;
totalDeposits -= amount;
// 3. INTERACTIONS (external calls)
IERC20(musdToken).safeTransfer(msg.sender, amount);
}2. Access Control
Role-Based Permissions:
We use OpenZeppelin's AccessControl for granular permissions:
import "@openzeppelin/contracts/access/AccessControl.sol";
contract YieldAggregator is AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant YIELD_MANAGER_ROLE = keccak256("YIELD_MANAGER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function distributeYield() external onlyRole(YIELD_MANAGER_ROLE) {
// Only authorized yield managers can call
}
function pause() external onlyRole(ADMIN_ROLE) {
// Only admins can pause in emergency
}
}Role Separation:
| Role | Permissions | Used For |
|---|---|---|
DEFAULT_ADMIN_ROLE | Manage other roles | Emergency only |
ADMIN_ROLE | Pause contracts | Security incidents |
YIELD_MANAGER_ROLE | Distribute yields | Daily operations |
UPGRADER_ROLE | Upgrade contracts | Planned upgrades (future) |
Admin roles are controlled by multi-sig wallets requiring multiple signatures for any action.
3. Pausable Contracts
Emergency Stop Mechanism:
Contracts can be paused in case of security incidents:
import "@openzeppelin/contracts/security/Pausable.sol";
contract IndividualPool is Pausable, AccessControl {
function deposit(uint256 amount) external whenNotPaused {
// Deposits disabled when paused
}
function withdraw(uint256 amount) external {
// Withdrawals ALWAYS enabled (even when paused)
}
function pause() external onlyRole(ADMIN_ROLE) {
_pause();
emit EmergencyPause(msg.sender, block.timestamp);
}
function unpause() external onlyRole(ADMIN_ROLE) {
_unpause();
emit EmergencyUnpause(msg.sender, block.timestamp);
}
}Important: Withdrawals remain enabled even when paused, ensuring users can always access their funds.
4. Safe Math & Overflow Protection
Solidity 0.8+ Built-in Protection:
We use Solidity 0.8.27 which has built-in overflow/underflow protection:
// Automatically reverts on overflow/underflow
uint256 public totalDeposits;
function deposit(uint256 amount) external {
// Safe addition - reverts if overflow
totalDeposits += amount;
// Safe subtraction - reverts if underflow
balances[msg.sender] += amount;
}Additional SafeMath for Complex Operations:
For edge cases, we use OpenZeppelin's SafeMath:
using SafeMath for uint256;
function calculateYield(uint256 principal, uint256 rate) internal pure returns (uint256) {
// Prevents overflow in multiplication
return principal.mul(rate).div(10000);
}5. Input Validation
Comprehensive Validation:
All external inputs are validated:
function deposit(uint256 amount) external nonReentrant whenNotPaused {
require(amount > 0, "Amount must be greater than zero");
require(amount <= MAX_DEPOSIT, "Amount exceeds maximum");
require(amount >= MIN_DEPOSIT, "Amount below minimum");
require(msg.sender != address(0), "Invalid sender");
// Additional business logic validation
require(totalDeposits + amount <= POOL_CAP, "Pool capacity exceeded");
_processDeposit(msg.sender, amount);
}Address Validation:
modifier validAddress(address addr) {
require(addr != address(0), "Zero address not allowed");
require(addr != address(this), "Self-reference not allowed");
_;
}
function setTokenAddress(address token) external onlyAdmin validAddress(token) {
musdToken = token;
}6. Event Logging
Complete Audit Trail:
Every state change emits events for transparency and monitoring:
event Deposited(address indexed user, uint256 amount, uint256 timestamp);
event Withdrawn(address indexed user, uint256 amount, uint256 timestamp);
event YieldDistributed(uint256 totalAmount, uint256 timestamp);
event EmergencyPause(address indexed admin, uint256 timestamp);
function deposit(uint256 amount) external {
// ... deposit logic ...
emit Deposited(msg.sender, amount, block.timestamp);
}Why This Matters:
- ✅ Off-chain monitoring can detect unusual activity
- ✅ Users can verify all interactions on-chain
- ✅ Complete transaction history is preserved
- ✅ Supports forensic analysis if needed
Multi-Signature Protection
Current Implementation
Admin Multi-Sig (Planned Q1 2026):
Critical operations will require multiple signatures:
- Pause/Unpause contracts: 2-of-3 signatures required
- Update yield rates: 2-of-3 signatures required
- Contract upgrades: 3-of-5 signatures required (future)
Multi-Sig Setup:
Signers: 5 team members
Threshold: 3 required for execution
Implementation: Gnosis Safe on MezoWhy Multi-Sig Matters
Single Point of Failure Prevention:
Without multi-sig: ❌ One compromised admin key = full contract control ❌ No checks and balances on admin actions ❌ Higher risk of malicious or accidental errors
With multi-sig: ✅ Multiple independent parties must agree ✅ Transparent governance process ✅ Protection against key compromise ✅ Time for community review
Example Multi-Sig Process:
- Proposal: Admin proposes action (e.g., "Pause contracts due to vulnerability")
- Review: Other signers review proposal independently
- Sign: Each signer approves or rejects
- Execute: After threshold met (3-of-5), action executes
- Verify: Community can see all signatures on-chain
Time-Lock Protection (Planned)
What is a Time-Lock?
A time-lock enforces a delay between proposing and executing critical actions.
Example:
// Planned implementation
contract TimelockController {
uint256 public constant MIN_DELAY = 2 days;
function schedulePause() external onlyMultiSig {
// Schedule pause for 2 days from now
bytes32 id = hashOperation(address(pool), data);
schedule[id] = block.timestamp + MIN_DELAY;
emit PauseScheduled(id, block.timestamp + MIN_DELAY);
}
function executePause(bytes32 id) external {
require(block.timestamp >= schedule[id], "Time-lock not expired");
// Execute the pause
pool.pause();
}
}Benefits
Community Protection:
✅ 48-hour warning before critical actions ✅ Time to react if action is malicious ✅ Transparent governance - all actions visible ✅ Emergency override only for critical security issues
What Gets Time-Locked:
- Contract upgrades (future)
- Yield rate changes
- Pool parameter updates
- Admin role changes
What's Exempt (Immediate Execution):
- Emergency pause (security incidents)
- User withdrawals (always instant)
- Yield distributions (routine operations)
Security Audits
Our contracts undergo multiple security reviews:
Automated Audits
✅ Slither - Static analysis (completed January 2025) ✅ Aderyn - Rust-based analyzer (completed January 2025) ✅ Mythril - Symbolic execution (in progress)
Manual Audits
🔄 Professional audit - Scheduled Q1 2026 🔄 Economic security review - Scheduled Q2 2026
Continuous Monitoring
Automated Scanning:
Every commit is scanned:
# GitHub Actions
- name: Run Slither
run: slither . --config-file slither.config.json
- name: Run Aderyn
run: aderyn . --output aderyn-report.mdOn-Chain Monitoring:
- Real-time event monitoring
- Unusual transaction detection
- Gas spike alerts
- Failed transaction analysis
Upgrade Strategy
Current Status
Non-Upgradeable Contracts:
Our current contracts are immutable for maximum security:
✅ Cannot be changed after deployment ✅ No hidden upgrade mechanisms ✅ Complete transparency - what you see is what you get
Migration Strategy:
If we need to fix bugs or add features:
- Deploy new contract version
- Announce migration with 30-day notice
- Users migrate funds voluntarily
- Old contract remains accessible forever
Future Upgradeability (Planned)
If we implement upgradeable contracts:
We will use OpenZeppelin's transparent proxy pattern with:
- ✅ Multi-sig ownership (3-of-5 required)
- ✅ Time-lock on upgrades (48-hour delay)
- ✅ Community governance (future)
- ✅ Emergency pause (no time-lock)
Upgrade Process:
1. Propose upgrade (multi-sig initiates)
2. Publish new implementation code (GitHub + audit)
3. Community review period (48+ hours)
4. Multi-sig approval (3-of-5 signatures)
5. Time-lock expires
6. Execution (anyone can trigger)
7. Post-upgrade verificationOracle Security
Yield Calculation
Current Implementation:
Yield rates are calculated off-chain and verified on-chain:
function distributeYield(
address[] calldata users,
uint256[] calldata amounts,
bytes calldata proof
) external onlyRole(YIELD_MANAGER_ROLE) {
// Verify merkle proof
require(_verifyYieldProof(users, amounts, proof), "Invalid proof");
// Distribute yields
for (uint i = 0; i < users.length; i++) {
_distributeToUser(users[i], amounts[i]);
}
}Security Measures:
✅ Merkle proofs verify calculation integrity ✅ Multiple independent nodes calculate yields ✅ Sanity checks prevent extreme values ✅ Rate limits prevent excessive distributions
Future Oracle Integration
Planned Improvements:
- Chainlink price feeds for BTC/USD conversion
- Decentralized yield verification
- On-chain calculation where possible
Gas Optimization & Security
We prioritize security over gas savings:
Safe Patterns We Use
✅ SafeERC20 instead of raw transfer()
✅ ReentrancyGuard on all state-changing functions
✅ Explicit checks instead of assumptions
✅ Event emissions for all state changes
Gas Optimizations (Carefully)
Where safe to optimize:
✅ Packed structs for storage efficiency ✅ Unchecked arithmetic only when guaranteed safe ✅ Batch operations for gas savings ✅ Efficient data structures
Where we DON'T optimize:
❌ Skip security checks to save gas ❌ Use assembly unless thoroughly audited ❌ Remove event emissions ❌ Reduce input validation
Example: Safe Gas Optimization
// Struct packing saves storage slots
struct Deposit {
uint128 amount; // 16 bytes
uint64 timestamp; // 8 bytes
uint64 lastClaim; // 8 bytes
// Total: 32 bytes = 1 storage slot instead of 3
}
// Batch processing saves gas
function batchWithdraw(uint256[] calldata amounts) external {
for (uint i = 0; i < amounts.length; i++) {
_withdraw(msg.sender, amounts[i]);
}
}Testing Strategy
Our test coverage targets:
- ✅ 80%+ code coverage (unit tests)
- ✅ 100% critical path coverage (core functions)
- ✅ Fuzz testing for edge cases
- ✅ Integration tests for cross-contract interactions
Test Categories:
Unit Tests
// Test every function in isolation
function testDeposit() public {
uint256 amount = 1000e18;
vm.prank(user);
pool.deposit(amount);
assertEq(pool.balanceOf(user), amount);
}
function testCannotDepositZero() public {
vm.prank(user);
vm.expectRevert("Amount must be greater than zero");
pool.deposit(0);
}Fuzz Testing
// Test with random inputs
function testFuzz_DepositWithdraw(uint256 amount) public {
vm.assume(amount > MIN_DEPOSIT && amount < MAX_DEPOSIT);
vm.prank(user);
pool.deposit(amount);
pool.withdraw(amount);
assertEq(pool.balanceOf(user), 0);
}Integration Tests
// Test full user flows
function testFullDepositYieldWithdrawFlow() public {
// 1. User deposits
deposit(user, 1000e18);
// 2. Yield is distributed
distributeYield();
// 3. User withdraws principal + yield
withdraw(user, pool.balanceOf(user));
// Verify final state
assertGt(musd.balanceOf(user), initialBalance);
}Emergency Procedures
Security Incident Response
If a vulnerability is discovered:
1. Immediate Response (< 1 hour)
- Pause affected contracts
- Notify multi-sig signers
- Alert community on Discord/Twitter
2. Assessment (< 24 hours)
- Analyze vulnerability severity
- Determine affected users
- Calculate potential impact
3. Mitigation (< 48 hours)
- Deploy fix if possible
- Plan user migration if needed
- Coordinate with auditors
4. Communication (Ongoing)
- Regular updates to community
- Publish detailed post-mortem
- Compensate affected users if applicable
Open Source & Transparency
Full Transparency:
✅ Source code: github.com/khipuvault/khipuvault ✅ Verified contracts: All contracts verified on Mezo explorer ✅ Audit reports: Published on this documentation ✅ Test coverage: Public CI/CD pipeline
Verify Contract Source:
- Go to Mezo Block Explorer
- Search for contract address
- Click "Contract" tab
- Verify source code matches GitHub
Security Commitment
We commit to:
✅ Regular audits - Quarterly automated, annual professional ✅ Responsible disclosure - Bug bounty program with rewards ✅ Community transparency - All security updates public ✅ Continuous improvement - Implement best practices as they evolve ✅ User protection - Your funds are always accessible
Questions?
Security questions or concerns?
- 📧 Email: security@khipuvault.com
- 💬 Discord: #security channel
- 📖 FAQ: Security FAQ
- 🐛 Bug Bounty: Report vulnerabilities
Your security is our priority. Thank you for trusting KhipuVault with your Bitcoin. 🛡️