Skip to content

Commit edc0648

Browse files
committed
feat: PrecompileEnvironment.ReentrancyGuard()
1 parent e35febe commit edc0648

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

core/vm/contracts.libevm.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ type PrecompileEnvironment interface {
190190
// Invalidate invalidates the transaction calling this precompile.
191191
InvalidateExecution(error)
192192

193+
// ReentrancyGuard ...
194+
ReentrancyGuard(key []byte) error
195+
193196
// Call is equivalent to [EVM.Call] except that the `caller` argument is
194197
// removed and automatically determined according to the type of call that
195198
// invoked the precompile.

core/vm/contracts.libevm_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,3 +839,45 @@ func ExamplePrecompileEnvironment() {
839839
// variable to include it in this example function.
840840
_ = actualCaller
841841
}
842+
843+
func TestReentrancyGuard(t *testing.T) {
844+
sut := common.HexToAddress("7E57ED")
845+
eve := common.HexToAddress("BAD")
846+
eveCalled := false
847+
848+
zero := func() *uint256.Int {
849+
return uint256.NewInt(0)
850+
}
851+
852+
returnIfGuarded := []byte("guarded")
853+
854+
hooks := &hookstest.Stub{
855+
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
856+
eve: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte) (ret []byte, err error) {
857+
eveCalled = true
858+
return env.Call(sut, []byte{}, env.Gas(), zero()) // i.e. reenter
859+
}),
860+
sut: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte) (ret []byte, err error) {
861+
// The argument is optional and used only to allow more than one
862+
// guard in a contract.
863+
if err := env.ReentrancyGuard(nil); err != nil {
864+
return returnIfGuarded, err
865+
}
866+
if env.Addresses().Caller == eve {
867+
// A real precompile MUST NOT panic under any circumstances.
868+
// It is done here to avoid a loop should the guard not
869+
// work.
870+
panic("reentrancy")
871+
}
872+
return env.Call(eve, []byte{}, env.Gas(), zero())
873+
}),
874+
},
875+
}
876+
hooks.Register(t)
877+
878+
_, evm := ethtest.NewZeroEVM(t)
879+
got, _, err := evm.Call(vm.AccountRef{}, sut, []byte{}, 1e6, zero())
880+
require.True(t, eveCalled, "Malicious contract called")
881+
assert.Equal(t, err, vm.ErrExecutionReverted, "Precompile reverted")
882+
assert.Equal(t, returnIfGuarded, got, "Precompile reverted with expected data")
883+
}

core/vm/environment.libevm.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/ava-labs/libevm/common"
2626
"github.com/ava-labs/libevm/common/math"
2727
"github.com/ava-labs/libevm/core/types"
28+
"github.com/ava-labs/libevm/crypto"
2829
"github.com/ava-labs/libevm/libevm"
2930
"github.com/ava-labs/libevm/libevm/options"
3031
"github.com/ava-labs/libevm/params"
@@ -103,6 +104,21 @@ func (e *environment) BlockHeader() (types.Header, error) {
103104
return *hdr, nil
104105
}
105106

107+
func reentrancyGuardSlot(key []byte) common.Hash {
108+
return crypto.Keccak256Hash([]byte("libevm-reentrancy-guard"), key)
109+
}
110+
111+
func (e *environment) ReentrancyGuard(key []byte) error {
112+
self := e.Addresses().Self
113+
slot := reentrancyGuardSlot(key)
114+
115+
if e.evm.StateDB.GetTransientState(self, slot) != (common.Hash{}) {
116+
return ErrExecutionReverted
117+
}
118+
e.evm.StateDB.SetTransientState(e.Addresses().Self, slot, common.Hash{1})
119+
return nil
120+
}
121+
106122
func (e *environment) Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) ([]byte, error) {
107123
return e.callContract(Call, addr, input, gas, value, opts...)
108124
}

0 commit comments

Comments
 (0)