argotorg / argotorg/solidity

Why does `staticcall` require using `view` even for precompiled contracts?

Open
#16,378 0 comments 0 reactions 0 assignees View on GitHub
feature
Dominant language
C++
Stars
25.7k
Forks
6.2k
Avg merge
1d 11h
Merged PRs (30d)
21

Description

## Abstract

It would be nice to be able to write `ECDSA` as `pure` function.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

/**
* @dev Library for low-level memory interaction.
*/
library Memory {
/**
* @dev Allocates chunk of memory of unbounded size.
* @dev Does not update free memory pointer.
* @return memPtr Pointer to allocated memory.
*/
function allocateUnbounded() internal pure returns (uint256 memPtr) {
// https://github.com/ethereum/solidity/blob/v0.8.30/libsolidity/codegen/YulUtilFunctions.cpp#L3211
assembly ("memory-safe") {
memPtr := mload(0x40)
}
}

/**
* @dev Writes word to memory at given offset.
* @param memPtr Pointer to memory.
* @param offset Offset in memory.
* @param value Word to write.
*/
function writeWord(uint256 memPtr, uint256 offset, uint256 value) internal pure {
// https://evm.codes/#52
assembly ("memory-safe") {
mstore(add(memPtr, offset), value)
}
}
}

/**
* @dev Library for low-level ECDSA recovery.
*/
library ECDSA {
/**
* @dev Recovers Ethereum address from ECDSA signature.
* `ecrecover(e, v, r, s)` works according to formula $Q = r^{-1} \( sR - eG \)$
* from https://secg.org/sec1-v2.pdf#subsubsection.4.1.6.
* @param memPtr Memory pointer for writing 128 bytes of input data.
* @param e Message hash, can be any 256-bit number,
* will be reduced to valid scalar (can be zero scalar).
* @param v Recovery ID, can be 27 or 28.
* Point `R(x, y)` has `yParity = v - 27`, `y` is calculated from `yParity`.
* @param r Non-zero scalar r, must be in `[1, Secp256k1.N)` and `x = r` must be on curve.
* Point `R(x, y)` has coordinate `x = r`.
* @param s Non-zero scalar s, must be in `[1, Secp256k1.N)`.
* @return recovered 160-bit Ethereum address of `Q` point.
* @dev If `v, r, s` do not satisfy above conditions, then `recovered = 0`
*/
function recover(uint256 memPtr, uint256 e, uint256 v, uint256 r, uint256 s)
internal
view
returns (uint256 recovered)
{
// https://github.com/ethereum/solidity/blob/v0.8.30/libsolidity/codegen/ir/IRGeneratorForStatements.cpp#L1653
Memory.writeWord(memPtr, 0x00, e);
Memory.writeWord(memPtr, 0x20, v);
Memory.writeWord(memPtr, 0x40, r);
Memory.writeWord(memPtr, 0x60, s);

Memory.writeWord(0x00, 0x00, 0x00);

// https://evm.codes/precompiled#0x01
assembly ("memory-safe") {
pop(staticcall(gas(), 0x01, memPtr, 0x80, 0x00, 0x20))
recovered := mload(0x00)
}
}
}

contract Demo {
constructor() payable {}

// pure
function recover1(bytes32 e, uint8 v, bytes32 r, bytes32 s) public pure returns(address) {
return ecrecover(e, v, r, s);
}

// view
function recover2(bytes32 e, uint8 v, bytes32 r, bytes32 s) public view returns(address) {
uint256 memPtr = Memory.allocateUnbounded();
return address(uint160(ECDSA.recover(memPtr, uint256(e), v, uint256(r), uint256(s))));
}
}
```

## Motivation

This can be useful for optimization. For example, suppose you're building a cryptographic library in Solidity. There's no point in using `view` if it's actually `pure`.

Contributor guide

Open the contributing guide

Research direction

Start with the referenced IRGeneratorForStatements.cpp and YulUtilFunctions.cpp locations, then trace how staticcall is classified for pure and view functions. Use the provided Demo contract as the reproduction case and inspect existing compiler tests for mutability checks. Done means the intended precompiled-contract call is accepted as pure without weakening other mutability rules.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, solidity
Domain
blockchain, compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.