This page is a placeholder. All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.
Overview
Opcode: 0x3e
Introduced: Byzantium (EIP-211)
RETURNDATACOPY copies return data from the most recent external call into memory.
Specification
Stack Input:
destOffset (memory offset)
offset (returndata offset)
length (bytes to copy)
Stack Output:
Gas Cost: 3 + memory expansion + (length / 32) * 3
Behavior
Copies length bytes from return data at offset to memory at destOffset. Reverts if offset + length exceeds return data size.
Key difference from other copy opcodes:
- Does NOT zero-pad - reverts on out-of-bounds access
- Strict bounds checking prevents reading beyond return data
Examples
Basic Usage
function copyReturnData() public {
address target = 0x...;
target.call("");
assembly {
let size := returndatasize()
returndatacopy(0, 0, size)
return(0, size)
}
}
Proxy Forwarding
fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
Error Bubbling
function bubbleRevert(address target, bytes memory data) public {
(bool success,) = target.call(data);
if (!success) {
assembly {
let size := returndatasize()
returndatacopy(0, 0, size)
revert(0, size)
}
}
}
Gas Cost
Base: 3 gas
Memory expansion: Variable
Copy cost: 3 gas per 32-byte word
Common Usage
Efficient Proxy
function delegate(address impl, bytes memory data) public returns (bytes memory) {
assembly {
let result := delegatecall(gas(), impl, add(data, 0x20), mload(data), 0, 0)
let size := returndatasize()
let output := mload(0x40)
mstore(output, size)
returndatacopy(add(output, 0x20), 0, size)
mstore(0x40, add(add(output, 0x20), size))
switch result
case 0 { revert(add(output, 0x20), size) }
default { return(add(output, 0x20), size) }
}
}
Security
Out-of-Bounds Reverts
Unlike CALLDATACOPY/CODECOPY, RETURNDATACOPY reverts on out-of-bounds:
function outOfBounds() public {
address(0).call(""); // Returns empty
assembly {
// This REVERTS because returndatasize() = 0
returndatacopy(0, 0, 32) // OutOfBounds!
}
}
Safe Pattern
function safeReturnCopy(uint256 offset, uint256 length) public {
require(offset + length <= returndatasize(), "Out of bounds");
assembly {
returndatacopy(0, offset, length)
}
}
Implementation
export function returndatacopy(frame: FrameType): EvmError | null {
const destOffsetResult = popStack(frame);
if (destOffsetResult.error) return destOffsetResult.error;
const offsetResult = popStack(frame);
if (offsetResult.error) return offsetResult.error;
const lengthResult = popStack(frame);
if (lengthResult.error) return lengthResult.error;
const offset = Number(offsetResult.value);
const length = Number(lengthResult.value);
// Strict bounds check - REVERTS if out of bounds
if (offset > frame.returnData.length ||
length > frame.returnData.length - offset) {
return { type: "OutOfBounds" };
}
// Copy returndata to memory
// See full implementation in codebase
frame.pc += 1;
return null;
}
Edge Cases
Empty Return Data
address(0).call("");
assembly {
returndatacopy(0, 0, 0) // OK: copying 0 bytes
returndatacopy(0, 0, 1) // REVERTS: out of bounds
}
Partial Copy
// returndata = 64 bytes
assembly {
returndatacopy(0, 0, 32) // OK: first 32 bytes
returndatacopy(0, 32, 32) // OK: second 32 bytes
returndatacopy(0, 64, 1) // REVERTS: beyond bounds
}
References