Skip to content

Commit 49f2312

Browse files
committed
feat: view and pure mutability limitations
1 parent 85dc089 commit 49f2312

File tree

7 files changed

+265
-22
lines changed

7 files changed

+265
-22
lines changed

core/vm/contracts.libevm.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,22 @@ type PrecompileEnvironment interface {
143143
Rules() params.Rules
144144
// StateDB will be non-nil i.f.f !ReadOnly().
145145
StateDB() StateDB
146-
// ReadOnlyState will always be non-nil.
146+
// ReadOnlyState will be non-nil i.f.f. SetPure() has not been called.
147147
ReadOnlyState() libevm.StateReader
148148

149+
// SetReadOnly ensures that all future calls to ReadOnly() will return true.
150+
// Is can be used as a guard against accidental writes when a read-only
151+
// function is invoked with EVM call() instead of staticcall().
152+
SetReadOnly()
153+
// SetPure ensures that all future calls to ReadOnly() will return true and
154+
// that calls to ReadOnlyState() will return nil.
155+
// TODO(arr4n) DO NOT MERGE: change this and SetReadOnly to return new
156+
// environments.
157+
SetPure()
158+
ReadOnly() bool
159+
149160
IncomingCallType() CallType
150161
Addresses() *libevm.AddressContext
151-
ReadOnly() bool
152162
// Equivalent to respective methods on [Contract].
153163
Gas() uint64
154164
UseGas(uint64) (hasEnoughGas bool)

core/vm/environment.libevm.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,21 @@ import (
3333
var _ PrecompileEnvironment = (*environment)(nil)
3434

3535
type environment struct {
36-
evm *EVM
37-
self *Contract
38-
callType CallType
36+
evm *EVM
37+
self *Contract
38+
callType CallType
39+
view, pure bool
3940
}
4041

4142
func (e *environment) Gas() uint64 { return e.self.Gas }
4243
func (e *environment) UseGas(gas uint64) bool { return e.self.UseGas(gas) }
4344
func (e *environment) Value() *uint256.Int { return new(uint256.Int).Set(e.self.Value()) }
4445

45-
func (e *environment) ChainConfig() *params.ChainConfig { return e.evm.chainConfig }
46-
func (e *environment) Rules() params.Rules { return e.evm.chainRules }
47-
func (e *environment) ReadOnlyState() libevm.StateReader { return e.evm.StateDB }
48-
func (e *environment) IncomingCallType() CallType { return e.callType }
49-
func (e *environment) BlockNumber() *big.Int { return new(big.Int).Set(e.evm.Context.BlockNumber) }
50-
func (e *environment) BlockTime() uint64 { return e.evm.Context.Time }
46+
func (e *environment) ChainConfig() *params.ChainConfig { return e.evm.chainConfig }
47+
func (e *environment) Rules() params.Rules { return e.evm.chainRules }
48+
func (e *environment) IncomingCallType() CallType { return e.callType }
49+
func (e *environment) BlockNumber() *big.Int { return new(big.Int).Set(e.evm.Context.BlockNumber) }
50+
func (e *environment) BlockTime() uint64 { return e.evm.Context.Time }
5151

5252
func (e *environment) refundGas(add uint64) error {
5353
gas, overflow := math.SafeAdd(e.self.Gas, add)
@@ -58,6 +58,9 @@ func (e *environment) refundGas(add uint64) error {
5858
return nil
5959
}
6060

61+
func (e *environment) SetReadOnly() { e.view = true }
62+
func (e *environment) SetPure() { e.pure = true }
63+
6164
func (e *environment) ReadOnly() bool {
6265
// A switch statement provides clearer code coverage for difficult-to-test
6366
// cases.
@@ -69,17 +72,18 @@ func (e *environment) ReadOnly() bool {
6972
return true
7073
case e.evm.interpreter.readOnly:
7174
return true
75+
case e.view || e.pure:
76+
return true
7277
default:
7378
return false
7479
}
7580
}
7681

77-
func (e *environment) Addresses() *libevm.AddressContext {
78-
return &libevm.AddressContext{
79-
Origin: e.evm.Origin,
80-
Caller: e.self.CallerAddress,
81-
Self: e.self.Address(),
82+
func (e *environment) ReadOnlyState() libevm.StateReader {
83+
if e.pure {
84+
return nil
8285
}
86+
return e.evm.StateDB
8387
}
8488

8589
func (e *environment) StateDB() StateDB {
@@ -89,6 +93,14 @@ func (e *environment) StateDB() StateDB {
8993
return e.evm.StateDB
9094
}
9195

96+
func (e *environment) Addresses() *libevm.AddressContext {
97+
return &libevm.AddressContext{
98+
Origin: e.evm.Origin,
99+
Caller: e.self.CallerAddress,
100+
Self: e.self.Address(),
101+
}
102+
}
103+
92104
func (e *environment) BlockHeader() (types.Header, error) {
93105
hdr := e.evm.Context.Header
94106
if hdr == nil {

libevm/precompilegen/Test.sol

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ interface IPrecompile {
1818
function Self() external view returns (address);
1919

2020
function RevertWith(bytes memory) external;
21+
22+
function View() external view returns (bool canReadState, bool canWriteState);
23+
24+
function Pure() external pure returns (bool canReadState, bool canWriteState);
25+
26+
function NeitherViewNorPure() external returns (bool canReadState, bool canWriteState);
2127
}
2228

2329
/// @dev Testing contract to exercise the implementaiton of `IPrecompile`.
@@ -75,4 +81,30 @@ contract PrecompileTest {
7581

7682
emit Called("EchoingFallback(...)");
7783
}
84+
85+
function assertReadOnly(bytes4 selector, bool expectCanReadState, bool expectCanWriteState) internal {
86+
// We use a low-level call() here to ensure that the Solidity compiler
87+
// doesn't issue a staticcall() to view/pure functions as this would
88+
// hide our forcing of a read-only state.
89+
(bool ok, bytes memory ret) = address(precompile).call(abi.encodeWithSelector(selector));
90+
assert(ok);
91+
(bool canReadState, bool canWriteState) = abi.decode(ret, (bool, bool));
92+
assert(canReadState == expectCanReadState);
93+
assert(canWriteState == expectCanWriteState);
94+
}
95+
96+
function View() external {
97+
assertReadOnly(IPrecompile.View.selector, true, false);
98+
emit Called("View()");
99+
}
100+
101+
function Pure() external {
102+
assertReadOnly(IPrecompile.Pure.selector, false, false);
103+
emit Called("Pure()");
104+
}
105+
106+
function NeitherViewNorPure() external {
107+
assertReadOnly(IPrecompile.NeitherViewNorPure.selector, true, true);
108+
emit Called("NeitherViewNorPure()");
109+
}
78110
}

0 commit comments

Comments
 (0)