ethermint
ethermint copied to clipboard
Support stateful precompiled contract
Proposal: Support stateful precompiled contract
Current behavior:
EVM support registering precompiled smart contract, but we can't simply modify the state in it, pitfalls of doing that, basically it doesn't play well with the statedb's snapshot and revert.
Desired behavior: Make stateful precompiled smart contract possible.
Use case:
Precompiled smart contract is a powerful tool for many use cases.
Allow precompile to extend statedb in memory logic somehow.
type StatefulPrecompiled interface {
PrecompiledContract
Commit(ctx sdk.Context) error
}
type ExtStateDB interface {
StateDB
AppendJournalEntry(entry journalEntry)
}
type StateDB struct {
precompiles map[Address]StatefulPrecompiled
}
func (db *StateDB) Commit(ctx sdk.Context) error {
for _, contract := range db.precompiles {
if err := contract.Commit(ctx); err != nil {
return err
}
}
}
db := NewStateDB()
db.precompiles := {
addr: NewExampleContract(ctx, db),
}
evm := NewEVM(db, db.precompiles)
evm.Call(...)
type ExampleContract struct {
db StateDB
state ExampleState
}
func NewExampleContract(ctx sdk.Context, db StateDB) *ExampleContract {
return &ExampleContract {
db: db,
state: loadState(ctx),
}
}
func (p *ExampleContract) RequiredGas(input []byte) uint64 {
// TODO
}
func (p *ExampleContract) Run(input []byte) ([]byte, error) {
p.db.append(revertEntry{
p: p,
saved: p.state,
})
// modify p.state in memory
}
func (p *ExampleContract) Commit(ctx sdk.Context) error {
// write p.state into cosmos-sdk storage.
}
// implements journalEntry interface
type revertEntry struct {
p *ExampleContract
saved ExampleState
}
func (r revertEntry) revert(*StateDB) {
r.p.state = r.saved
}
func (r revertEntry) dirtied() *common.Address {
// TODO
}
An example bank transfer precompiled contract:
type BankTransferContract struct {
bankKeeper BankKeeper
cachedCtx sdk.Context
writeCache CacheWriter
stateDB ExtStateDB
}
func NewBankTransferContract(ctx sdk.Context, bankKeeper BankKeeer, stateDB StateDB) *BankTransferContract {
cachedCtx, writeCache := ctx.CacheContext()
return &BankTransferContract{
bankKeeper, cachedCtx, writeCache, stateDB,
}
}
func (bc *BankTransferContract) Run(input []byte) error {
from, to, amount := decodeInput(input);
if err := bc.bankKeeper.Transfer(bc.cachedCtx, from, to, amount); err != nil {
return err
}
bc.stateDB.AppendJournalEntry(revertBankEntry{
bc.bankKeeper, bc.cachedCtx, from, to, amount,
})
}
func (bc *BankContract) Commit(ctx sdk.Context) error {
bc.writeCache()
}
type revertBankEntry struct {
bankKeeper BankKeeper
ctx sdk.Context
from, to, amount
}
func (r revertBankEntry) revert(*StateDB) {
if err := r.bankKeeper.Transfer(r.ctx, to, from, amount); err != nil {
// invariant, should not fail.
}
}
func (r revertBankEntry) dirtied() *common.Address {
return nil
}
should look into making special stripped out go-ethereum fork specifically for ethermint.
(Think SubnetEVM)
(Maybe that's even the place to start)
This issue is stale because it has been open 45 days with no activity. Remove Status: Stale label or comment or this will be closed in 7 days.
unstale
This issue is stale because it has been open 45 days with no activity. Remove Status: Stale label or comment or this will be closed in 7 days.