RustyChains

975 posts

RustyChains banner
RustyChains

RustyChains

@RustyChains101

Blockchain, Solidity, DeFi 🪿

Se unió Kasım 2024
687 Siguiendo1.1K Seguidores
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: What does codesize() return if called within the constructor? What about outside the constructor? This is a nuanced EVM behavior with a real security implication Outside the constructor (normal calls): codesize() returns the size of the contract's deployed bytecode — the runtime code stored on-chain. For any deployed contract, this is always > 0 Inside the constructor: codesize() returns the size of the initcode — the deployment bytecode, which includes the constructor logic itself plus the runtime code it will deploy. This is also > 0, but it's a different value entirely The critical distinction: EXTCODESIZE vs. CODESIZE EXTCODESIZE(addr) checks another address's deployed code size. A classic anti-contract check uses: require(msg.sender.code.length == 0); During a constructor call, the contract being deployed has no runtime code yet — EXTCODESIZE returns 0. So this check passes for contracts calling you from their constructor, silently breaking the "EOA only" assumption codesize() (no argument) always refers to the currently executing code — initcode during deployment, runtime code afterward. It's not useful as an EOA check The takeaway: - codesize() inside constructor → size of initcode - codesize() outside constructor → size of runtime bytecode - EXTCODESIZE == 0 does NOT reliably prove the caller is an EOA
English
0
1
6
240
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: Without using the % operator, how can you determine if a number is even or odd? One bitwise AND operation: bool isOdd = (x & 1) == 1; Here's why it works All integers in binary have a least significant bit (LSB) — the rightmost bit. By definition: - If LSB is 0 → the number is even - If LSB is 1 → the number is odd AND-ing any number with 1 isolates that LSB entirely, zeroing out all other bits: 5 = 0b...00000101 & 1 = 1 → odd 8 = 0b...00001000 & 1 = 0 → even Beyond correctness, this matters for gas The EVM's AND opcode costs 3 gas. The MOD opcode (%) also costs 5 gas — but more importantly, bitwise operations signal intent to the optimizer and are idiomatic in low-level EVM code In Yul or heavily optimized Solidity, you'll see (x & 1) everywhere for this reason. It's not just a trick — it's the canonical low-level pattern for parity checks across virtually every computing environment, from embedded systems to the EVM
English
0
1
6
327
0x
0x@0xProject·
Make 0x your next home: → Senior Backend Engineer, Matcha → Growth Product Manager, Matcha → Senior Mobile Engineer, Matcha → Sr. Account Executive, 0x → Senior Product Designer, 0x/Matcha Apply below ⤵️ #open-positions" target="_blank" rel="nofollow noopener">0x.org/careers#open-p…
English
4
1
28
3.1K
0x
0x@0xProject·
We're hiring at 0x 💞 Across Engineering, Product, and GTM. Shoot your shot in the comments. Or apply below 👇
English
28
11
213
24.1K
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: What is the danger of using return in assembly out of a Solidity function that has a modifier? This is a subtle but critical trap that can silently break modifier logic First, understand how modifiers work under the hood. Solidity compiles modifiers by inlining the function body at the position of the _ placeholder. The modifier's post-_ code runs after the function body returns function foo() external myModifier { // function body inlined here } modifier myModifier() { // pre-checks _; // function body executes here // post-logic — runs after function body } Now here's the problem A Solidity-level return respects this inlining. It exits the function body and hands control back to the modifier, which continues executing its post-_ logic An assembly-level return does not assembly { return(ptr, size) } is a raw EVM RETURN opcode. It immediately terminates the entire call frame — no cleanup, no continuation. The modifier's post-_ code never executes This means: - Reentrancy guards that reset a flag after _ - never reset, looks locked forever or worse - Access control post-checks - silently skipped - State cleanup logic - never runs The contract appears to work correctly in testing — but under specific conditions, the modifier's guarantees are completely voided Rule: never use assembly return inside a function with modifiers that have post-_ logic. Use Solidity-level returns, or restructure to avoid the conflict
English
0
0
12
607
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: Describe how to compute the 9th root of a number on-chain in Solidity There's no native root opcode in the EVM. But you have two solid approaches Option 1: Newton's Method (iterative approximation) Newton's method converges on a root by iteratively refining a guess: x_new = ((n-1) * x_old + val / x_old^(n-1)) / n For the 9th root (n=9): - Start with an initial guess (e.g. the value itself or a binary search estimate) - Each iteration: x = (8 * x + val / x^8) / 9 - Repeat until x_new >= x_old (convergence) In Solidity with fixed-point integers, this converges in ~100 iterations for uint256. Gas-intensive but fully on-chain and exact to integer precision Option 2: Binary Search Search for the largest integer x such that x^9 <= val: - lo = 0, hi = val - mid = (lo + hi) / 2 - If mid^9 <= val: lo = mid, else hi = mid - Repeat until convergence Computingmid^9 naively risks overflow — use mulmod or a checked exponentiation helper. Binary search is simpler to reason about but slower to converge than Newton's method Option 3: Verify off-chain, check on-chain The most gas-efficient pattern: compute the root off-chain, pass it in, and verify on-chain: require(root**9 <= val && (root+1)**9 > val); One exponentiation check instead of a full iterative loop. This is the production-grade approach for most protocols For most on-chain use cases, verify-don't-compute is the right default
English
0
0
8
451
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: Why shouldn’t you get price from slot0 in Uniswap V3? slot0 is the first storage slot of a Uniswap V3 pool. It contains sqrtPriceX96 — the current price of the pool. It's right there, cheap to read, and tempting to use Don't slot0 reflects the price after the last swap in the current block. That makes it trivially manipulable within a single transaction: - Attacker takes a large flash loan - Executes a massive swap, moving slot0 price significantly - Calls your contract, which reads slot0 as "the price" - Your protocol misprices collateral, mints excess tokens, or liquidates incorrectly - Attacker reverses the swap and repays the flash loan All in one transaction. No capital required. This exact vector has been exploited in multiple DeFi hacks The correct alternative: use Uniswap V3's built-in TWAP oracle observe() returns time-weighted average prices over a configurable window. A 30-minute TWAP requires an attacker to sustain price manipulation across many blocks — enormously expensive and almost always unprofitable The tradeoff: TWAPs lag real-time price. For latency-sensitive use cases, consider Chainlink or a hybrid approach Rule: slot0 is for internal pool logic. For any external price consumption — collateral, liquidations, minting — always use a TWAP or a dedicated oracle
English
1
0
12
863
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: What is SECP256K1? SECP256K1 is the elliptic curve that underpins public/private key cryptography in both Bitcoin and Ethereum. Every wallet, every signature, every address derivation relies on it The name breaks down as: - SEC: Standards for Efficient Cryptography - P: prime field - 256: 256-bit key size - K: Koblitz curve (a specific curve family) - 1: first curve of this type in the standard The curve is defined over a prime field by the equation: y² = x³ + 7 This specific form (a=0, b=7) is what makes it a Koblitz curve — and gives it performance advantages over more generic curves like NIST's P-256 How it's used in Ethereum: - Your private key is a random 256-bit integer - Your public key is a point on the curve: pubKey = privKey × G (where G is the generator point) - Your address is the last 20 bytes of keccak256(pubKey) The security assumption: given a public key, computing the private key requires solving the elliptic curve discrete logarithm problem — computationally infeasible with current technology One nuance: SECP256K1 was relatively obscure before Bitcoin. Most standards bodies favored NIST curves. Satoshi's choice of it — possibly to avoid curves with opaque NIST parameter selection — is one of the more consequential decisions in crypto history
English
2
2
28
1.8K
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: What is a nullifier in the context of zero knowledge, and what is it used for? In ZK protocols, a nullifier is a public value derived from a private secret that proves a specific action has occurred — without revealing who performed it or what the secret is The classic problem it solves: double spending in privacy-preserving systems Consider Zcash or Tornado Cash. A user deposits funds and receives a private note (a secret). Later they withdraw by proving, via a ZK proof, that they own a valid note. But without a nullifier, the same note could be used to withdraw multiple times — and since everything is private, no one would know The nullifier fixes this: - It's computed deterministically from the private note: nullifier = hash(secret) - It's revealed publicly at withdrawal time - The contract stores all used nullifiers - If a nullifier was already seen - reject. If not - allow and record it Critically, the nullifier reveals nothing about the original secret or the depositor's identity. It's just a unique fingerprint of the private note — unlinkable to the deposit transaction Nullifiers are foundational in: private payments (Zcash, Tornado Cash), ZK identity systems, private voting, and anonymous credential schemes Privacy without double-spend protection is broken. Nullifiers are what make it whole
English
0
0
13
578
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: What does a metaproxy do? A metaproxy (EIP-3448) is a minimal proxy pattern — like EIP-1167 clones — but with one key upgrade: it appends arbitrary immutable data to the bytecode at deployment time A standard EIP-1167 clone delegates all calls to a fixed implementation address. Every clone is identical. If you need instance-specific configuration, you'd have to store it in storage — which costs gas on every read A metaproxy solves this by encoding instance-specific data directly into the contract's bytecode as a suffix. That data is read via CODECOPY, not SLOAD — making it cheaper to access and immutable by design The deployed bytecode looks like: [minimal proxy logic] + [ABI-encoded immutable data] At runtime, the implementation contract reads its own code to retrieve the appended data: address implementation = ...; // fixed in bytecode bytes memory data = ...; // copied from code suffix This is useful when: - You're deploying thousands of instances with slightly different configs - The config never changes after deployment - You want to avoid per-instance storage costs Real-world use cases: factory-deployed pools, per-user vaults, or any pattern where a single implementation serves many parameterized instances eips.ethereum.org/EIPS/eip-3448
English
0
0
1
230
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: What is a zk-friendly hash function and how does it differ from a non-zk-friendly hash function? Zero-knowledge proof systems (SNARKs, STARKs) don't execute code like the EVM does. They work by expressing computation as arithmetic circuits over a finite field — essentially systems of equations with additions and multiplications Traditional hash functions like SHA-256 or Keccak-256 are designed for hardware and software efficiency. They rely heavily on bitwise operations (XOR, AND, bit shifts) — operations that are cheap on CPUs but translate into enormous arithmetic circuits. A single SHA-256 hash can require tens of thousands of constraints in a ZK circuit ZK-friendly hash functions — Poseidon, MiMC, Pedersen — are designed from the ground up to minimize arithmetic constraints. They operate natively over the same prime fields ZK proof systems use, replacing bitwise ops with field additions and multiplications. The result: Poseidon uses ~200-300 constraints vs. ~25,000+ for SHA-256 in a ZK circuit The tradeoff: ZK-friendly hashes are slower and less battle-tested outside ZK contexts. Keccak remains the standard for EVM compatibility
English
0
0
6
554
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: Why do negative numbers in calldata cost more gas? It comes down to zero bytes vs. non-zero bytes EIP-2028 defines calldata pricing: - Zero bytes (0x00) cost 4 gas each - Non-zero bytes cost 16 gas each Small positive integers are cheap because they ABI-encode to mostly zero bytes. uint256(1) is: 0x0000000000000000000000000000000000000000000000000000000000000001 31 zero bytes + 1 non-zero byte = 31×4 + 1×16 = 140 gas Negative integers, following two's complement, are padded with 0xFF — non-zero bytes. int256(-1) is: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 32 non-zero bytes = 32×16 = 512 gas That's 3.6× more expensive for the same 32-byte slot Practical takeaway: if your protocol passes signed integers in calldata frequently, consider encoding strategies that minimize non-zero bytes — or reconsider whether signed types are necessary at all
English
0
0
1
187
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: What is the use of the signextend opcode? The EVM natively operates on 256-bit words. But Solidity supports smaller signed integers: int8, int32, int128, and so on When you load a small signed integer into a 256-bit stack slot, the upper bytes are zero-padded. That's fine for unsigned values — but wrong for negative signed ones Consider int8(-1): in 8-bit two's complement it's 0xFF. But in a 256-bit word, 0x00...00FF is +255, not -1 SIGNEXTEND fixes this. It takes: - b: the byte size minus 1 (e.g. 0 for int8, 1 for int16) - x: the value to extend And propagates the sign bit across all upper bytes: 0xFF -> 0xFFFF...FFFF (-1 in 256-bit two's complement) Solidity emits SIGNEXTEND automatically when casting or operating on signed integer types smaller than 256 bits. You'll rarely call it directly in Yul — but it's what makes int8 arithmetic semantically correct at the EVM level Without it, signed arithmetic on sub-256 types would silently produce wrong results
English
0
0
7
477
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: What does an int256 variable that stores -1 look like in hex? 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF That's 32 bytes — 64 F's. And it's not a coincidence. Solidity uses two's complement to represent signed integers, just like most CPUs In two's complement: - Flip all bits of the positive value - Add 1 For -1: flip all bits of 0x000...001 -> 0xFFF...FFE, then add 1 -> 0xFFF...FFF The result is all bits set to 1 — which is -1 for any signed integer width, whether it's int8 or int256 This has practical implications in Solidity: - type(int256).max == 0x7FFFFFFF...FFFF (MSB = 0) - type(int256).min == 0x80000000...0000 (MSB = 1) - -1 unchecked cast to uint256 == type(uint256).max That last point is a classic source of bugs. If you ever cast a negative int to a uint without checking, you get a massive number — not an error
English
0
0
13
907
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: How much gas can be forwarded in a call to another smart contract? By default, when you make an external call in Solidity, nearly all remaining gas is forwarded. But "nearly all" has a precise definition: EIP-150 (the 63/64 rule) caps it The 63/64 rule: a call can forward at most 63/64 of the remaining gas. The caller always retains at least 1/64 as a safety buffer to handle the call returning This applies recursively. Deep call stacks progressively reduce available gas at each level — which is exactly the point. It prevents call depth attacks that could drain gas entirely You can also set gas explicitly: - someContract.call{gas: 50000}(...) — forward exactly 50,000 gas - transfer() and send() — hardcoded to 2,300 gas (enough for an event, nothing more) One important edge case: if you manually specify more gas than the 63/64 limit allows, the EVM silently caps it. You won't get an error — you'll just forward less than you asked for The takeaway: default calls forward ~all gas minus 1/64. Explicit gas limits give you control. And transfer()/send() are intentionally restrictive — which is why most modern contracts avoid them
English
1
1
12
605
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: What does the verbatim keyword do, and where can it be used? verbatim is a Yul-only built-in that lets you inject raw bytecode directly into compiled output, bypassing the optimizer entirely The syntax looks like this: verbatim_2i_1o(hex"600a600b01", a, b) The suffix encodes the stack effect: 2 inputs, 1 output. The optimizer treats it as a black box and won't touch it Why does this matter? The Solidity optimizer is powerful — but it can't reason about arbitrary bytecode. verbatim lets you insert opcodes that Yul doesn't natively expose, or guarantee exact bytecode output when it's critical (e.g. in interpreter loops or low-level precompile wrappers) Constraints to know: - Only available in Yul / inline assembly blocks, not in regular Solidity - Not available in Solidity expressions — pure Yul only - Stack inputs/outputs must be declared explicitly and match reality - The optimizer will not reorder, remove, or simplify it In practice, verbatim is a tool for compiler developers and protocol engineers working at the lowest level of the EVM — not something you'll reach for in everyday contract development But knowing it exists tells you something important: Yul is designed to give you complete control, all the way down to the byte
English
1
1
10
289
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: Why did Solidity deprecate the “years” keyword? It seems harmless — 1 years == 365 days, right? The problem: that's not always true Leap years exist. A calendar year is 365.25 days on average, meaning time-sensitive logic using "years" could silently drift by ~6 hours per year. For financial contracts or governance with year-long time locks, that's a real issue Solidity's time unit keywords (seconds, minutes, hours, days, weeks) are all fixed-duration — unambiguous and safe. But "years" implied calendar accuracy it couldn't deliver It was removed in Solidity 0.5.0 to prevent developers from building false assumptions into their contracts The lesson: in smart contracts, time is just seconds. If you need calendar-aware logic, handle it explicitly — don't let a keyword do it for you
English
1
1
17
1.5K
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: Under what conditions does the Openzeppelin Proxy.sol overwrite the free memory pointer? Why is it safe to do this? In _delegate(), the entire function is written in raw assembly. It copies calldata directly to memory address 0x00, performs a delegatecall, then copies return data back to 0x00: - calldatacopy(0, 0, calldatasize()) - delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) - returndatacopy(0, 0, returndatasize()) This deliberately ignores the free memory pointer at 0x40 Why is it safe? - The function is 100% assembly. No Solidity variables or ABI encoding rely on 0x40 being valid - Execution never returns to Solidity. The function always terminates with return() or revert() — so no Solidity code ever sees the corrupted pointer - 0x00–0x3f is scratch space by design. The EVM memory layout reserves it for short-term use. It's only Solidity's allocator that needs 0x40 to be correct The free memory pointer is a Solidity convention, not an EVM rule. In a fully self-contained assembly context, it simply doesn't apply
English
0
0
10
266
Alexa Web3 (e/acc)
Alexa Web3 (e/acc)@alexabelonix·
@RustyChains101 good work, keep going. we’re hiring DevRel ambassadors for paid campaigns. fill the form in my pinned post
English
1
0
1
35
RustyChains
RustyChains@RustyChains101·
Solidity Interview Q: How can you validate on-chain that another smart contract emitted an event, without using an oracle? Short answer: not directly. EVM events live in transaction receipts, outside the state trie, so the EVM can't read them at runtime But there are trustless alternatives: - Read storage instead of events. If you control both contracts, expose a getter function. Events are for off-chain consumers; storage is for on-chain consumers - Callback pattern. Have the source contract call back into your verifier at execution time. Simple and oracle-free - Storage proofs. Prove the state of another contract at a specific block using Merkle-Patricia proofs. Used in bridges and cross-chain protocols - ZK proofs. Tools like Axiom or Risc Zero let you prove historical on-chain execution and verify it trustlessly on-chain The most common mistake? Treating events as an on-chain data source. They're not — they're logs for off-chain indexers If you need cross-contract verification, design around state, not events
English
1
0
8
351