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: 0x34
Introduced: Frontier (EVM genesis)
CALLVALUE pushes the amount of wei sent with the current call onto the stack. This corresponds to msg.value in Solidity.
Specification
Stack Input:
Stack Output:
Gas Cost: 2 (GasQuickStep)
Operation:
stack.push(execution_context.value)
Behavior
CALLVALUE provides access to the wei amount sent with the current message call. This value is always 0 for STATICCALL and DELEGATECALL.
Key characteristics:
- Returns wei amount (1 ether = 10^18 wei)
- Always 0 for STATICCALL (no value transfer allowed)
- Preserved in DELEGATECALL (uses caller’s value)
- New value for each CALL
Examples
Basic Usage
import { callvalue } from '@tevm/voltaire/evm/context';
import { createFrame } from '@tevm/voltaire/evm/Frame';
// 1 ETH sent with this call
const frame = createFrame({
value: 1000000000000000000n, // 1 ETH in wei
stack: []
});
const err = callvalue(frame);
console.log(frame.stack[0]); // 1000000000000000000n
Payable Function
contract PaymentReceiver {
event PaymentReceived(address from, uint256 amount);
function pay() public payable {
require(msg.value > 0, "No payment");
emit PaymentReceived(msg.sender, msg.value);
}
function checkValue() public payable returns (uint256) {
return msg.value; // Uses CALLVALUE opcode
}
}
Deposit Pattern
contract Vault {
mapping(address => uint256) public balances;
function deposit() public payable {
require(msg.value > 0, "Must send ETH");
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
Gas Cost
Cost: 2 gas (GasQuickStep)
Same as other environment context opcodes:
- ADDRESS (0x30): 2 gas
- ORIGIN (0x32): 2 gas
- CALLER (0x33): 2 gas
Common Usage
Minimum Payment
contract MinimumPayment {
uint256 public constant MINIMUM = 0.1 ether;
function purchase() public payable {
require(msg.value >= MINIMUM, "Insufficient payment");
// Process purchase
}
}
Exact Payment
contract FixedPrice {
uint256 public constant PRICE = 1 ether;
function buyItem() public payable {
require(msg.value == PRICE, "Incorrect payment");
// Transfer item
}
function buyWithRefund() public payable {
require(msg.value >= PRICE, "Insufficient payment");
// Refund excess
if (msg.value > PRICE) {
payable(msg.sender).transfer(msg.value - PRICE);
}
// Transfer item
}
}
Value Forwarding
contract Forwarder {
address public recipient;
function forward() public payable {
require(msg.value > 0, "No value to forward");
payable(recipient).transfer(msg.value);
}
function forwardWithFee(uint256 feePercent) public payable {
require(msg.value > 0);
uint256 fee = (msg.value * feePercent) / 100;
uint256 remainder = msg.value - fee;
payable(owner).transfer(fee);
payable(recipient).transfer(remainder);
}
}
Crowdfunding
contract Crowdfund {
uint256 public goal;
uint256 public raised;
mapping(address => uint256) public contributions;
function contribute() public payable {
require(msg.value > 0);
contributions[msg.sender] += msg.value;
raised += msg.value;
}
function refund() public {
require(raised < goal, "Goal reached");
uint256 amount = contributions[msg.sender];
require(amount > 0);
contributions[msg.sender] = 0;
raised -= amount;
payable(msg.sender).transfer(amount);
}
}
Security
Payable vs Non-Payable
// Non-payable: rejects ETH
function nonPayable() public {
// Compiler inserts: require(msg.value == 0)
// Reverts if msg.value > 0
}
// Payable: accepts ETH
function payable() public payable {
// Can receive ETH
}
Reentrancy with Value
// VULNERABLE
contract Vulnerable {
mapping(address => uint256) public balances;
function withdraw() public {
uint256 amount = balances[msg.sender];
// DANGEROUS: external call before state update
payable(msg.sender).call{value: amount}("");
balances[msg.sender] = 0;
}
}
// Attacker can reenter with msg.value = 0
contract Attacker {
Vulnerable victim;
receive() external payable {
if (address(victim).balance > 0) {
victim.withdraw(); // Reenter
}
}
}
Safe pattern:
contract Safe {
mapping(address => uint256) public balances;
function withdraw() public {
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0; // State update first
payable(msg.sender).transfer(amount);
}
}
Value Conservation
contract ValueSplitter {
function split(address[] memory recipients) public payable {
require(recipients.length > 0);
uint256 share = msg.value / recipients.length;
// ISSUE: msg.value might not divide evenly
for (uint i = 0; i < recipients.length; i++) {
payable(recipients[i]).transfer(share);
}
// Dust remains in contract!
}
}
Better pattern:
function splitExact(address[] memory recipients) public payable {
uint256 count = recipients.length;
uint256 share = msg.value / count;
uint256 remainder = msg.value % count;
for (uint i = 0; i < count; i++) {
payable(recipients[i]).transfer(share);
}
// Return remainder to sender
if (remainder > 0) {
payable(msg.sender).transfer(remainder);
}
}
DELEGATECALL Value Preservation
contract Implementation {
function getValue() public payable returns (uint256) {
return msg.value;
}
}
contract Proxy {
function proxyGetValue(address impl) public payable returns (uint256) {
(bool success, bytes memory data) = impl.delegatecall(
abi.encodeWithSignature("getValue()")
);
require(success);
return abi.decode(data, (uint256));
}
}
// If Proxy called with 1 ETH:
// - In Implementation: msg.value = 1 ETH (preserved via delegatecall)
Implementation
import { consumeGas } from "../Frame/consumeGas.js";
import { pushStack } from "../Frame/pushStack.js";
/**
* CALLVALUE opcode (0x34) - Get deposited value in current call
*
* Stack: [] => [value]
* Gas: 2 (GasQuickStep)
*/
export function callvalue(frame: FrameType): EvmError | null {
const gasErr = consumeGas(frame, 2n);
if (gasErr) return gasErr;
const pushErr = pushStack(frame, frame.value);
if (pushErr) return pushErr;
frame.pc += 1;
return null;
}
Edge Cases
Zero Value
// Call with no value
const frame = createFrame({ value: 0n });
callvalue(frame);
console.log(frame.stack[0]); // 0n
Maximum Value
// Maximum possible value (impractical but valid)
const MAX_U256 = (1n << 256n) - 1n;
const frame = createFrame({ value: MAX_U256 });
callvalue(frame);
console.log(frame.stack[0]); // MAX_U256
Stack Overflow
const frame = createFrame({
value: 1000n,
stack: new Array(1024).fill(0n)
});
const err = callvalue(frame);
console.log(err); // { type: "StackOverflow" }
References