go-ethereum/libevm/reentrancy/guard.go
Arran Schlosberg 910e897c54
feat: reentrancy.Guard for stateful precompiles (#212)
## Why this should be merged

The `vm.PrecompileEnvironment.Call()` method requires careful usage
because of reentrancy vulnerabilities (this is common to all outgoing
`CALL`s in the EVM, not just stateful precompiles). This package
provides a common method of protection, a reentrancy guard.

## How this works

Provides a function that returns `vm.ErrExecutionReverted` if called
twice, by the same contract, in the same transaction, with the same
identifier.

## How this was tested

Unit and integration tests.
2025-10-03 19:56:29 +00:00

55 lines
2.2 KiB
Go

// Copyright 2025 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// The libevm additions are distributed in the hope that they will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see
// <http://www.gnu.org/licenses/>.
// Package reentrancy provides a reentrancy guard for stateful precompiles that
// make outgoing calls to other contracts.
//
// Reentrancy occurs when the contract (C) called by a precompile (P) makes a
// further call back into P, which may result in theft of funds (see DAO hack).
// A reentrancy guard detects these recursive calls and reverts.
package reentrancy
import (
"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/core/vm"
"github.com/ava-labs/libevm/crypto"
"github.com/ava-labs/libevm/libevm"
)
var slotPreimagePrefix = []byte("libevm-reentrancy-guard-")
// Guard returns [vm.ErrExecutionReverted] i.f.f. it has already been called
// with the same `key`, by the same contract, in the same transaction. It
// otherwise returns nil. The `key` MAY be nil.
//
// Contract equality is defined as the [libevm.AddressContext] "self" address
// being the same under EVM semantics.
func Guard(env vm.PrecompileEnvironment, key []byte) error {
self := env.Addresses().EVMSemantic.Self
slot := crypto.Keccak256Hash(slotPreimagePrefix, key)
sdb := env.StateDB()
if sdb.GetTransientState(self, slot) != (common.Hash{}) {
return vm.ErrExecutionReverted
}
sdb.SetTransientState(self, slot, common.Hash{1})
return nil
}
// Keep the `libevm` import to allow the linked comment on [Guard]. The package
// is imported by `vm` anyway so this is a noop but it improves developer
// experience.
var _ = (*libevm.AddressContext)(nil)