Smart contract bugs aren't like regular bugs. In traditional software, you deploy a fix. In Solidity, once a contract is on-chain, it's immutable. Every vulnerability is permanent and publicly exploitable. Here's what I learned building a crowdfunding platform on Ethereum.

The Challenge

I was building a decentralized crowdfunding platform where users could create campaigns, contribute ETH, and receive automated refunds if campaigns failed to meet their goals. The smart contracts would handle real value — ETH that users deposited — which meant security wasn't optional, it was existential. A reentrancy bug or integer overflow could drain every campaign's funds instantly.

The Goal

Design and implement Solidity smart contracts that securely managed campaign creation, ETH contributions, milestone-based fund release, and automated refunds. The contracts needed to be gas-efficient, resistant to common attack vectors (reentrancy, integer overflow, front-running), and thoroughly tested before any testnet deployment.

The Approach

The most important architectural decision was implementing the withdrawal pattern instead of push payments. Rather than having the contract send ETH to users (which opens reentrancy attack surfaces), I let users withdraw their own funds. This simple pattern eliminates an entire class of vulnerabilities.

For the campaign logic, I used a state machine pattern — each campaign progresses through states (Active, Successful, Failed, Finalized) with strict transition rules enforced by modifiers. Fund release is milestone-based: campaign creators can only withdraw portions of funds when predefined milestones are met, verified by contributor votes. If a campaign fails, contributors can withdraw their deposits without any action from the campaign creator.

Testing was done with Hardhat's local blockchain, writing tests in JavaScript that simulated edge cases: what happens when a contributor tries to withdraw twice, when a campaign creator tries to finalize before the deadline, when gas prices spike during a refund. I used OpenZeppelin's SafeMath (pre-Solidity 0.8) patterns and ReentrancyGuard as a defense-in-depth measure even with the withdrawal pattern.

The Impact

The platform deployed successfully on Ethereum testnet with zero security issues detected during testing. The withdrawal pattern eliminated reentrancy concerns entirely. The state machine architecture made the contract logic auditable — anyone could read the Solidity code and understand exactly what state transitions were possible. Gas optimization through efficient storage patterns kept transaction costs reasonable for users.

Key Takeaways

The withdrawal pattern should be your default for any contract that sends ETH. State machines make contract logic explicit and auditable. Test for edge cases that would never happen in traditional software — in smart contracts, attackers will find them. Use established patterns from OpenZeppelin rather than reinventing security primitives. And always remember: immutability means every bug is a permanent bug.

Questions? kanade.pra@northeastern.edu