By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
DeFi primitives are the building blocks that let anyone create financial services on‑chain without a bank. They include lending markets, automated market makers (AMMs), staking contracts, and yield‑farming strategies. By composably linking these pieces, developers can launch a full‑featured DeFi app in weeks instead of years. Real‑world example: A user swaps USDC for DAI on Uniswap (an AMM), then deposits the DAI into Aave (a lending pool) to earn interest, and finally stakes the Aave‑aUSDC receipt token in a Curve pool to capture extra yield.
U = borrowed / total
solidity function getBorrowRate(uint256 cash, uint256 borrows) public pure returns (uint256) { uint256 utilization = borrows * 1e18 / (cash + borrows); return utilization * 0.05e18; // 5 % APR per 100 % utilization }
k
solidity function swap(uint amountIn, address tokenIn, address tokenOut) external { uint amountOut = getAmountOut(amountIn, tokenIn, tokenOut); IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn); IERC20(tokenOut).transfer(msg.sender, amountOut); }
sToken
solidity uint256 public rewardPerBlock; mapping(address => uint256) public rewardDebt; function updateRewards() internal { uint256 pending = (block.number - lastRewardBlock) * rewardPerBlock; accRewardPerShare += pending * 1e12 / totalStaked; }
js async function compound() { await vault.deposit(amount); await vault.harvest(); // claim rewards await vault.reinvest(); // add rewards back to the pool }
solidity uint256 private _status; modifier nonReentrant() { require(_status == 0, "REENTRANCY"); _status = 1; _; _status = 0; }
solidity function executeFlashLoan(address token, uint256 amount) external { lendingPool.flashLoan(address(this), token, amount, abi.encode(msg.sender)); }
LendingPool.sol
AMM.sol
StakingVault.sol
npx hardhat compile
chai
ethers
swap
borrow
stake
bash npx hardhat run scripts/deploy.js --network goerli
The script should deploy the three contracts, set the AMM pair address in the LendingPool, and grant the StakingVault permission to pull LP tokens. 4. Add front‑end interaction – In a React app, install ethers@6, connect MetaMask, and call:
ethers@6
js const pool = new ethers.Contract(poolAddr, poolAbi, signer); await pool.deposit(usdcAmount); const tx = await amm.swap(usdcAmount, USDC, DAI); await stakingVault.stake(lpTokenAmount); 5. Run a “compound” bot – Schedule the compound() script (step 4) with a cron job or a serverless function to harvest and reinvest rewards automatically.
js const pool = new ethers.Contract(poolAddr, poolAbi, signer); await pool.deposit(usdcAmount); const tx = await amm.swap(usdcAmount, USDC, DAI); await stakingVault.stake(lpTokenAmount);
compound()
Mistake: Using tx.origin for access control. Correction: Use msg.sender and role‑based modifiers (onlyOwner, AccessControl). tx.origin can be spoofed through a malicious contract, opening a phishing vector.
tx.origin
msg.sender
onlyOwner
AccessControl
Mistake: Forgetting to update the AMM’s invariant after a manual token transfer. Correction: Always call the AMM’s sync() or updateReserves() after any external token movement; otherwise price calculations become stale and arbitrageurs will drain the pool.
sync()
updateReserves()
Mistake: Not accounting for slippage when swapping large amounts. Correction: Compute minAmountOut with getAmountOut and pass it to the swap call; revert if the market moves beyond your tolerance.
minAmountOut
getAmountOut
Mistake: Deploying a staking contract without a nonReentrant guard. Correction: Add the nonReentrant modifier to any function that transfers user tokens (deposit, withdraw, claim). This blocks classic reentrancy attacks that could double‑spend rewards.
nonReentrant
Mistake: Assuming ERC‑20 transfer returns true and ignoring the return value. Correction: Use OpenZeppelin’s SafeERC20 wrapper (safeTransfer, safeTransferFrom) which reverts on failure and handles non‑standard tokens.
transfer
true
SafeERC20
safeTransfer
safeTransferFrom
delegatecall
tokensReceived
require(balanceAfter >= balanceBefore)
Scenario: A contract uses tx.origin to restrict withdrawals to the original user. Answer: Dangerous – a malicious contract can trick a user into calling it, and tx.origin will still be the user’s address, allowing the attacker to withdraw funds.
Scenario: You add 10 k USDC to a Uniswap V2 pool but forget to call sync(). Answer: The pool’s reserves stay unchanged, so the price oracle becomes stale; arbitrage bots will exploit the mismatch and you’ll lose value.
Scenario: A staking contract distributes rewards using block.timestamp instead of block.number. Answer: Timestamps can be manipulated by miners within a ~15‑second window, leading to slightly inaccurate reward calculations; using block numbers is deterministic.
block.timestamp
block.number
uint256
uint128
k = reserveX * reserveY
require(token.balanceOf(address(this)) >= amountBefore, "not repaid")
totalReward / (endBlock - startBlock)
safeTransferFrom(token, from, to, amount)
SafeMath
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.