Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: general code style cleanups on precompile and statedb journal #2100

Merged
merged 24 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
295a2d9
statedb: add cacheing for multistore before precompile runs
Unique-Divine Oct 25, 2024
80dd2d7
messy, working first version that allows for precompile reversion
Unique-Divine Oct 26, 2024
c624912
wip!: Save checkpoint.
Unique-Divine Oct 26, 2024
7f904a0
chore: changelog
Unique-Divine Oct 26, 2024
08a73ee
finalize bank keeper changes
Unique-Divine Oct 28, 2024
717ce1c
revert to previous commit 7f904a07ac4d5555d8c088411024fc50ff65d085
Unique-Divine Oct 29, 2024
04a6897
fix strange ignored file issue
Unique-Divine Oct 29, 2024
7c34423
remove new bank keeper
Unique-Divine Oct 29, 2024
4e3bf3c
chore: comments from self-review
Unique-Divine Oct 29, 2024
576cf6c
Merge branch 'main' into ud/db-cache
k-yang Oct 29, 2024
90fecf7
refactor: clean up test code
k-yang Oct 29, 2024
5fcb361
refactor: clean up parse fund args
k-yang Oct 29, 2024
c1bb21a
refactor: clean up common precompile functions
k-yang Oct 29, 2024
10885f1
refactor: clean up precompile onrunstart
k-yang Oct 29, 2024
f04b1e5
refactor: clean up precompile onrunstart
k-yang Oct 29, 2024
78c835a
refactor: clean up oracle precompile
k-yang Oct 29, 2024
5b38ec5
Merge branch 'main' into refactor/cleanup
k-yang Oct 29, 2024
d7db81b
fix: import
k-yang Oct 29, 2024
7236e22
Delete .gitignore
k-yang Oct 29, 2024
f6cccc7
Update CHANGELOG.md
k-yang Oct 29, 2024
2dc5646
refactor: replace interface{} with any
k-yang Oct 30, 2024
5bae79e
refactor: add ABI() precompile fn
k-yang Oct 30, 2024
94121a7
refactor: replace assertNotReadonlyTx
k-yang Oct 30, 2024
6dc3c9b
Merge branch 'main' into ud/db-cache
k-yang Oct 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ committed as expected, fixes the `StateDB.Commit` to follow its guidelines more
closely, and solves for a critical state inconsistency producible from the
FunToken.sol precompiled contract. It also aligns the precompiles to use
consistent setup and dynamic gas calculations, addressing the following tickets.
- https://github.com/NibiruChain/nibiru/issues/2083
- https://github.com/code-423n4/2024-10-nibiru-zenith/issues/43
- https://github.com/code-423n4/2024-10-nibiru-zenith/issues/47
- <https://github.com/NibiruChain/nibiru/issues/2083>
- <https://github.com/code-423n4/2024-10-nibiru-zenith/issues/43>
- <https://github.com/code-423n4/2024-10-nibiru-zenith/issues/47>
- [#2088](https://github.com/NibiruChain/nibiru/pull/2088) - refactor(evm): remove outdated comment and improper error message text
- [#2089](https://github.com/NibiruChain/nibiru/pull/2089) - better handling of gas consumption within erc20 contract execution
- [#2091](https://github.com/NibiruChain/nibiru/pull/2091) - feat(evm): add fun token creation fee validation
Expand Down Expand Up @@ -158,6 +158,7 @@ reverts inside of a try-catch.
- [#2060](https://github.com/NibiruChain/nibiru/pull/2060) - fix(evm-precompiles): add assertNumArgs validation
- [#2056](https://github.com/NibiruChain/nibiru/pull/2056) - feat(evm): add oracle precompile
- [#2065](https://github.com/NibiruChain/nibiru/pull/2065) - refactor(evm)!: Refactor out dead code from the evm.Params
- [#2100](https://github.com/NibiruChain/nibiru/pull/2100) - refactor: cleanup statedb and precompile sections

### State Machine Breaking (Other)

Expand Down
17 changes: 0 additions & 17 deletions x/evm/precompile/.gitignore

This file was deleted.

9 changes: 0 additions & 9 deletions x/evm/precompile/errors.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package precompile

import (
"errors"
"fmt"
"reflect"

Expand Down Expand Up @@ -34,14 +33,6 @@ func ErrMethodCalled(method *gethabi.Method, wrapped error) error {
return fmt.Errorf("%s method called: %w", method.Name, wrapped)
}

// Check required for transactions but not needed for queries
func assertNotReadonlyTx(readOnly bool, isTx bool) error {
if readOnly && isTx {
return errors.New("cannot write state from staticcall (a read-only call)")
Comment on lines -38 to -40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add the method name as an argument to keep the error messages consistent across each tx method using the same function

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally got rid of assertNotReadonlyTx because whenever it was invoked, the isTx argument was always true, which made that parameter redundant because assertNotReadonlyTx in a mutating precompile. I've replaced it and added the gethabi.Method as a parameter to extract the method name into the error string.

}
return nil
}

// assertContractQuery checks if a contract call is a valid query operation. This
// function verifies that no funds (wei) are being sent with the query, as query
// operations should be read-only and not involve any value transfer.
Expand Down
27 changes: 6 additions & 21 deletions x/evm/precompile/funtoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
import (
"fmt"
"math/big"
"sync"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth/types"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
gethabi "github.com/ethereum/go-ethereum/accounts/abi"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"

Expand All @@ -32,29 +30,23 @@
return PrecompileAddr_FunToken
}

func (p precompileFunToken) ABI() *gethabi.ABI {
return embeds.SmartContract_FunToken.ABI
}

// RequiredGas calculates the cost of calling the precompile in gas units.
func (p precompileFunToken) RequiredGas(input []byte) (gasCost uint64) {
return RequiredGas(input, p.ABI())
k-yang marked this conversation as resolved.
Show resolved Hide resolved
return requiredGas(input, embeds.SmartContract_FunToken.ABI)
}

const (
FunTokenMethod_BankSend PrecompileMethod = "bankSend"
)

type PrecompileMethod string

// Run runs the precompiled contract
func (p precompileFunToken) Run(
evm *vm.EVM, contract *vm.Contract, readonly bool,
) (bz []byte, err error) {
defer func() {
err = ErrPrecompileRun(err, p)
}()
start, err := OnRunStart(evm, contract, p.ABI())
start, err := OnRunStart(evm, contract.Input, embeds.SmartContract_FunToken.ABI)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -87,8 +79,6 @@
evmKeeper evmkeeper.Keeper
}

var executionGuard sync.Mutex

// bankSend: Implements "IFunToken.bankSend"
//
// The "args" populate the following function signature in Solidity:
Expand All @@ -105,15 +95,10 @@
caller gethcommon.Address,
readOnly bool,
) (bz []byte, err error) {
ctx, method, args := start.Ctx, start.Method, start.Args
if e := assertNotReadonlyTx(readOnly, true); e != nil {
err = e
return
}
if !executionGuard.TryLock() {
return nil, fmt.Errorf("bankSend is already in progress")
ctx, method, args := start.CacheCtx, start.Method, start.Args
if readOnly {
return nil, fmt.Errorf("bankSend cannot be called in read-only mode")

Check warning on line 100 in x/evm/precompile/funtoken.go

View check run for this annotation

Codecov / codecov/patch

x/evm/precompile/funtoken.go#L100

Added line #L100 was not covered by tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add test coverage for readOnly mode check

The readOnly check is a critical safety measure, but static analysis indicates it lacks test coverage. Please add test cases to verify this behavior.

Would you like me to help generate test cases for the readOnly mode check?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 100-100: x/evm/precompile/funtoken.go#L100
Added line #L100 was not covered by tests

}
defer executionGuard.Unlock()

erc20, amount, to, err := p.decomposeBankSendArgs(args)
if err != nil {
Expand Down Expand Up @@ -242,7 +227,7 @@
return nil
}

func (p precompileFunToken) decomposeBankSendArgs(args []any) (
func (p precompileFunToken) decomposeBankSendArgs(args []interface{}) (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using modern Go type syntax

The method signature uses []interface{} which is the pre-Go 1.18 syntax. Consider using the more modern []any type alias introduced in Go 1.18.

-func (p precompileFunToken) decomposeBankSendArgs(args []interface{}) (
+func (p precompileFunToken) decomposeBankSendArgs(args []any) (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (p precompileFunToken) decomposeBankSendArgs(args []interface{}) (
func (p precompileFunToken) decomposeBankSendArgs(args []any) (

erc20 gethcommon.Address,
amount *big.Int,
to string,
Expand Down
15 changes: 6 additions & 9 deletions x/evm/precompile/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (p precompileOracle) Address() gethcommon.Address {
}

func (p precompileOracle) RequiredGas(input []byte) (gasPrice uint64) {
return RequiredGas(input, embeds.SmartContract_Oracle.ABI)
return requiredGas(input, embeds.SmartContract_Oracle.ABI)
}

const (
Expand All @@ -38,21 +38,19 @@ func (p precompileOracle) Run(
defer func() {
err = ErrPrecompileRun(err, p)
}()
res, err := OnRunStart(evm, contract, embeds.SmartContract_Oracle.ABI)
startResult, err := OnRunStart(evm, contract.Input, embeds.SmartContract_Oracle.ABI)
if err != nil {
return nil, err
}
method, args, ctx := res.Method, res.Args, res.Ctx
method, args, ctx := startResult.Method, startResult.Args, startResult.CacheCtx

switch PrecompileMethod(method.Name) {
case OracleMethod_queryExchangeRate:
bz, err = p.queryExchangeRate(ctx, method, args, readonly)
return p.queryExchangeRate(ctx, method, args)
default:
err = fmt.Errorf("invalid method called with name \"%s\"", method.Name)
return
}

return
}

func PrecompileOracle(keepers keepers.PublicKeepers) vm.PrecompiledContract {
Expand All @@ -69,9 +67,8 @@ func (p precompileOracle) queryExchangeRate(
ctx sdk.Context,
method *gethabi.Method,
args []interface{},
readOnly bool,
) (bz []byte, err error) {
pair, err := p.decomposeQueryExchangeRateArgs(args)
pair, err := p.parseQueryExchangeRateArgs(args)
if err != nil {
return nil, err
}
Expand All @@ -88,7 +85,7 @@ func (p precompileOracle) queryExchangeRate(
return method.Outputs.Pack(price.String())
}

func (p precompileOracle) decomposeQueryExchangeRateArgs(args []any) (
func (p precompileOracle) parseQueryExchangeRateArgs(args []interface{}) (
pair string,
err error,
) {
Expand Down
43 changes: 22 additions & 21 deletions x/evm/precompile/precompile.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@
return nil, fmt.Errorf("no method with id: %#x", sigdata[:4])
}

func DecomposeInput(
func decomposeInput(
abi *gethabi.ABI, input []byte,
) (method *gethabi.Method, args []interface{}, err error) {
// ABI method IDs are exactly 4 bytes according to "gethabi.ABI.MethodByID".
if len(input) < 4 {
readableBz := collections.HumanizeBytes(input)
err = fmt.Errorf("input \"%s\" too short to extract method ID (less than 4 bytes)", readableBz)
err = fmt.Errorf("input \"%s\" too short to extract method ID (less than 4 bytes)", collections.HumanizeBytes(input))

Check warning on line 93 in x/evm/precompile/precompile.go

View check run for this annotation

Codecov / codecov/patch

x/evm/precompile/precompile.go#L93

Added line #L93 was not covered by tests
return
}

method, err = methodById(abi, input[:4])
if err != nil {
err = fmt.Errorf("unable to parse ABI method by its 4-byte ID: %w", err)
Expand All @@ -109,8 +109,8 @@
return method, args, nil
}

func RequiredGas(input []byte, abi *gethabi.ABI) uint64 {
method, _, err := DecomposeInput(abi, input)
func requiredGas(input []byte, abi *gethabi.ABI) uint64 {
method, err := methodById(abi, input[:4])
if err != nil {
// It's appropriate to return a reasonable default here
// because the error from DecomposeInput will be handled automatically by
Expand All @@ -122,26 +122,27 @@

// Map access could panic. We know that it won't panic because all methods
// are in the map, which is verified by unit tests.
methodIsTx := precompileMethodIsTxMap[PrecompileMethod(method.Name)]
var costPerByte, costFlat uint64
if methodIsTx {
if isMutation[PrecompileMethod(method.Name)] {
costPerByte, costFlat = gasCfg.WriteCostPerByte, gasCfg.WriteCostFlat
} else {
costPerByte, costFlat = gasCfg.ReadCostPerByte, gasCfg.ReadCostFlat
}

argsBzLen := uint64(len(input[4:]))
return (costPerByte * argsBzLen) + costFlat
// Calculate the total gas required based on the input size and flat cost
return (costPerByte * uint64(len(input[4:]))) + costFlat
}

type PrecompileMethod string

type OnRunStartResult struct {
// Args contains the decoded (ABI unpacked) arguments passed to the contract
// as input.
Args []any
Args []interface{}

// Ctx is a cached SDK context that allows isolated state
// CacheCtx is a cached SDK context that allows isolated state
// operations to occur that can be reverted by the EVM's [statedb.StateDB].
Ctx sdk.Context
CacheCtx sdk.Context

// Method is the ABI method for the precompiled contract call.
Method *gethabi.Method
Expand Down Expand Up @@ -178,9 +179,9 @@
// }
// ```
func OnRunStart(
evm *vm.EVM, contract *vm.Contract, abi *gethabi.ABI,
evm *vm.EVM, contractInput []byte, abi *gethabi.ABI,
) (res OnRunStartResult, err error) {
method, args, err := DecomposeInput(abi, contract.Input)
method, args, err := decomposeInput(abi, contractInput)
if err != nil {
return res, err
}
Expand All @@ -194,23 +195,23 @@
// journalEntry captures the state before precompile execution to enable
// proper state reversal if the call fails or if [statedb.JournalChange]
// is reverted in general.
cacheCtx, journalEntry := stateDB.CacheCtxForPrecompile(contract.Address())
if err = stateDB.SavePrecompileCalledJournalChange(contract.Address(), journalEntry); err != nil {
cacheCtx, journalEntry := stateDB.CacheCtxForPrecompile()
if err = stateDB.SavePrecompileCalledJournalChange(journalEntry); err != nil {
return res, err
}
if err = stateDB.CommitCacheCtx(); err != nil {
return res, fmt.Errorf("error committing dirty journal entries: %w", err)
}

return OnRunStartResult{
Args: args,
Ctx: cacheCtx,
Method: method,
StateDB: stateDB,
Args: args,
CacheCtx: cacheCtx,
Method: method,
StateDB: stateDB,
}, nil
}

var precompileMethodIsTxMap map[PrecompileMethod]bool = map[PrecompileMethod]bool{
var isMutation map[PrecompileMethod]bool = map[PrecompileMethod]bool{
WasmMethod_execute: true,
WasmMethod_instantiate: true,
WasmMethod_executeMulti: true,
Expand Down
Loading
Loading