Skip to content

Commit 679305d

Browse files
authored
refactor(precompiles): apply journal-based revert approach (#205)
* refactor(precompiles): apply journal-based revert approach * refactor: remove unused Snapshot type * chore: fix lint
1 parent 5cfe96d commit 679305d

File tree

10 files changed

+346
-387
lines changed

10 files changed

+346
-387
lines changed

precompiles/bank/bank.go

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -104,47 +104,41 @@ func (p Precompile) RequiredGas(input []byte) uint64 {
104104

105105
// Run executes the precompiled contract bank query methods defined in the ABI.
106106
func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) {
107-
ctx, stateDB, snapshot, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction)
107+
ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction)
108108
if err != nil {
109109
return nil, err
110110
}
111111

112112
// This handles any out of gas errors that may occur during the execution of a precompile query.
113113
// It avoids panics and returns the out of gas error so the EVM can continue gracefully.
114-
defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)()
115-
116-
return p.RunAtomic(
117-
snapshot,
118-
stateDB,
119-
func() ([]byte, error) {
120-
switch method.Name {
121-
// Bank queries
122-
case BalancesMethod:
123-
bz, err = p.Balances(ctx, contract, method, args)
124-
case TotalSupplyMethod:
125-
bz, err = p.TotalSupply(ctx, contract, method, args)
126-
case SupplyOfMethod:
127-
bz, err = p.SupplyOf(ctx, contract, method, args)
128-
default:
129-
return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name)
130-
}
131-
132-
if err != nil {
133-
return nil, err
134-
}
135-
136-
cost := ctx.GasMeter().GasConsumed() - initialGas
137-
138-
if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) {
139-
return nil, vm.ErrOutOfGas
140-
}
141-
if err := p.AddJournalEntries(stateDB, snapshot); err != nil {
142-
return nil, err
143-
}
144-
145-
return bz, nil
146-
},
147-
)
114+
defer cmn.HandleGasError(ctx, contract, initialGas, &err)()
115+
116+
switch method.Name {
117+
// Bank queries
118+
case BalancesMethod:
119+
bz, err = p.Balances(ctx, contract, method, args)
120+
case TotalSupplyMethod:
121+
bz, err = p.TotalSupply(ctx, contract, method, args)
122+
case SupplyOfMethod:
123+
bz, err = p.SupplyOf(ctx, contract, method, args)
124+
default:
125+
return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name)
126+
}
127+
128+
if err != nil {
129+
return nil, err
130+
}
131+
132+
cost := ctx.GasMeter().GasConsumed() - initialGas
133+
134+
if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) {
135+
return nil, vm.ErrOutOfGas
136+
}
137+
if err = p.AddJournalEntries(stateDB); err != nil {
138+
return nil, err
139+
}
140+
141+
return bz, nil
148142
}
149143

150144
// IsTransaction checks if the given method name corresponds to a transaction or query.

precompiles/common/precompile.go

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,6 @@ func NewBalanceChangeEntry(acc common.Address, amt *uint256.Int, op Operation) B
4545
return BalanceChangeEntry{acc, amt, op}
4646
}
4747

48-
// Snapshot contains all state and events previous to the precompile call
49-
// This is needed to allow us to revert the changes
50-
// during the EVM execution
51-
type Snapshot struct {
52-
MultiStore storetypes.CacheMultiStore
53-
Events sdk.Events
54-
}
55-
5648
// RequiredGas calculates the base minimum required gas for a transaction or a query.
5749
// It uses the method ID to determine if the input is a transaction or a query and
5850
// uses the Cosmos SDK gas config flat cost and the flat per byte cost * len(argBz) to calculate the gas.
@@ -66,46 +58,41 @@ func (p Precompile) RequiredGas(input []byte, isTransaction bool) uint64 {
6658
return p.KvGasConfig.ReadCostFlat + (p.KvGasConfig.ReadCostPerByte * uint64(len(argsBz)))
6759
}
6860

69-
// RunAtomic is used within the Run function of each Precompile implementation.
70-
// It handles rolling back to the provided snapshot if an error is returned from the core precompile logic.
71-
// Note: This is only required for stateful precompiles.
72-
func (p Precompile) RunAtomic(s Snapshot, stateDB *statedb.StateDB, fn func() ([]byte, error)) ([]byte, error) {
73-
bz, err := fn()
74-
if err != nil {
75-
// revert to snapshot on error
76-
stateDB.RevertMultiStore(s.MultiStore, s.Events)
77-
}
78-
return bz, err
79-
}
80-
8161
// RunSetup runs the initial setup required to run a transaction or a query.
8262
// It returns the sdk Context, EVM stateDB, ABI method, initial gas and calling arguments.
8363
func (p Precompile) RunSetup(
8464
evm *vm.EVM,
8565
contract *vm.Contract,
8666
readOnly bool,
8767
isTransaction func(name *abi.Method) bool,
88-
) (ctx sdk.Context, stateDB *statedb.StateDB, s Snapshot, method *abi.Method, gasConfig storetypes.Gas, args []interface{}, err error) {
68+
) (ctx sdk.Context, stateDB *statedb.StateDB, method *abi.Method, gasConfig storetypes.Gas, args []interface{}, err error) {
8969
stateDB, ok := evm.StateDB.(*statedb.StateDB)
9070
if !ok {
91-
return sdk.Context{}, nil, s, nil, uint64(0), nil, errors.New(ErrNotRunInEvm)
71+
return sdk.Context{}, nil, nil, uint64(0), nil, errors.New(ErrNotRunInEvm)
9272
}
9373

9474
// get the stateDB cache ctx
9575
ctx, err = stateDB.GetCacheContext()
9676
if err != nil {
97-
return sdk.Context{}, nil, s, nil, uint64(0), nil, err
77+
return sdk.Context{}, nil, nil, uint64(0), nil, err
9878
}
9979

10080
// take a snapshot of the current state before any changes
10181
// to be able to revert the changes
102-
s.MultiStore = stateDB.MultiStoreSnapshot()
103-
s.Events = ctx.EventManager().Events()
82+
snapshot := stateDB.MultiStoreSnapshot()
83+
events := ctx.EventManager().Events()
84+
85+
// add precompileCall entry on the stateDB journal
86+
// this allows to revert the changes within an evm txAdd commentMore actions
87+
err = stateDB.AddPrecompileFn(p.Address(), snapshot, events)
88+
if err != nil {
89+
return sdk.Context{}, nil, nil, uint64(0), nil, err
90+
}
10491

10592
// commit the current changes in the cache ctx
10693
// to get the updated state for the precompile call
10794
if err := stateDB.CommitWithCacheCtx(); err != nil {
108-
return sdk.Context{}, nil, s, nil, uint64(0), nil, err
95+
return sdk.Context{}, nil, nil, uint64(0), nil, err
10996
}
11097

11198
// NOTE: This is a special case where the calling transaction does not specify a function name.
@@ -131,26 +118,26 @@ func (p Precompile) RunSetup(
131118
}
132119

133120
if err != nil {
134-
return sdk.Context{}, nil, s, nil, uint64(0), nil, err
121+
return sdk.Context{}, nil, nil, uint64(0), nil, err
135122
}
136123

137124
// return error if trying to write to state during a read-only call
138125
if readOnly && isTransaction(method) {
139-
return sdk.Context{}, nil, s, nil, uint64(0), nil, vm.ErrWriteProtection
126+
return sdk.Context{}, nil, nil, uint64(0), nil, vm.ErrWriteProtection
140127
}
141128

142129
// if the method type is `function` continue looking for arguments
143130
if method.Type == abi.Function {
144131
argsBz := contract.Input[4:]
145132
args, err = method.Inputs.Unpack(argsBz)
146133
if err != nil {
147-
return sdk.Context{}, nil, s, nil, uint64(0), nil, err
134+
return sdk.Context{}, nil, nil, uint64(0), nil, err
148135
}
149136
}
150137

151138
initialGas := ctx.GasMeter().GasConsumed()
152139

153-
defer HandleGasError(ctx, contract, initialGas, &err, stateDB, s)()
140+
defer HandleGasError(ctx, contract, initialGas, &err)()
154141

155142
// set the default SDK gas configuration to track gas usage
156143
// we are changing the gas meter type, so it panics gracefully when out of gas
@@ -160,20 +147,16 @@ func (p Precompile) RunSetup(
160147
// we need to consume the gas that was already used by the EVM
161148
ctx.GasMeter().ConsumeGas(initialGas, "creating a new gas meter")
162149

163-
return ctx, stateDB, s, method, initialGas, args, nil
150+
return ctx, stateDB, method, initialGas, args, nil
164151
}
165152

166153
// HandleGasError handles the out of gas panic by resetting the gas meter and returning an error.
167154
// This is used in order to avoid panics and to allow for the EVM to continue cleanup if the tx or query run out of gas.
168-
func HandleGasError(ctx sdk.Context, contract *vm.Contract, initialGas storetypes.Gas, err *error, stateDB *statedb.StateDB, snapshot Snapshot) func() {
155+
func HandleGasError(ctx sdk.Context, contract *vm.Contract, initialGas storetypes.Gas, err *error) func() {
169156
return func() {
170157
if r := recover(); r != nil {
171158
switch r.(type) {
172159
case storetypes.ErrorOutOfGas:
173-
174-
// revert to snapshot on error
175-
stateDB.RevertMultiStore(snapshot.MultiStore, snapshot.Events)
176-
177160
// update contract gas
178161
usedGas := ctx.GasMeter().GasConsumed() - initialGas
179162
_ = contract.UseGas(usedGas, nil, tracing.GasChangeCallFailedExecution)
@@ -190,9 +173,7 @@ func HandleGasError(ctx sdk.Context, contract *vm.Contract, initialGas storetype
190173
}
191174

192175
// AddJournalEntries adds the balanceChange (if corresponds)
193-
// and precompileCall entries on the stateDB journal
194-
// This allows to revert the call changes within an evm tx
195-
func (p Precompile) AddJournalEntries(stateDB *statedb.StateDB, s Snapshot) error {
176+
func (p Precompile) AddJournalEntries(stateDB *statedb.StateDB) error {
196177
for _, entry := range p.journalEntries {
197178
switch entry.Op {
198179
case Sub:
@@ -204,7 +185,7 @@ func (p Precompile) AddJournalEntries(stateDB *statedb.StateDB, s Snapshot) erro
204185
}
205186
}
206187

207-
return stateDB.AddPrecompileFn(p.Address(), s.MultiStore, s.Events)
188+
return nil
208189
}
209190

210191
// SetBalanceChangeEntries sets the balanceChange entries

precompiles/distribution/distribution.go

Lines changed: 54 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -85,68 +85,66 @@ func (p Precompile) RequiredGas(input []byte) uint64 {
8585

8686
// Run executes the precompiled contract distribution methods defined in the ABI.
8787
func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) {
88-
ctx, stateDB, snapshot, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction)
88+
ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction)
8989
if err != nil {
9090
return nil, err
9191
}
9292

9393
// This handles any out of gas errors that may occur during the execution of a precompile tx or query.
9494
// It avoids panics and returns the out of gas error so the EVM can continue gracefully.
95-
defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)()
96-
97-
return p.RunAtomic(snapshot, stateDB, func() ([]byte, error) {
98-
switch method.Name {
99-
// Custom transactions
100-
case ClaimRewardsMethod:
101-
bz, err = p.ClaimRewards(ctx, contract, stateDB, method, args)
102-
// Distribution transactions
103-
case SetWithdrawAddressMethod:
104-
bz, err = p.SetWithdrawAddress(ctx, contract, stateDB, method, args)
105-
case WithdrawDelegatorRewardMethod:
106-
bz, err = p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args)
107-
case WithdrawValidatorCommissionMethod:
108-
bz, err = p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args)
109-
case FundCommunityPoolMethod:
110-
bz, err = p.FundCommunityPool(ctx, contract, stateDB, method, args)
111-
case DepositValidatorRewardsPoolMethod:
112-
bz, err = p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args)
113-
// Distribution queries
114-
case ValidatorDistributionInfoMethod:
115-
bz, err = p.ValidatorDistributionInfo(ctx, contract, method, args)
116-
case ValidatorOutstandingRewardsMethod:
117-
bz, err = p.ValidatorOutstandingRewards(ctx, contract, method, args)
118-
case ValidatorCommissionMethod:
119-
bz, err = p.ValidatorCommission(ctx, contract, method, args)
120-
case ValidatorSlashesMethod:
121-
bz, err = p.ValidatorSlashes(ctx, contract, method, args)
122-
case DelegationRewardsMethod:
123-
bz, err = p.DelegationRewards(ctx, contract, method, args)
124-
case DelegationTotalRewardsMethod:
125-
bz, err = p.DelegationTotalRewards(ctx, contract, method, args)
126-
case DelegatorValidatorsMethod:
127-
bz, err = p.DelegatorValidators(ctx, contract, method, args)
128-
case DelegatorWithdrawAddressMethod:
129-
bz, err = p.DelegatorWithdrawAddress(ctx, contract, method, args)
130-
case CommunityPoolMethod:
131-
bz, err = p.CommunityPool(ctx, contract, method, args)
132-
}
133-
134-
if err != nil {
135-
return nil, err
136-
}
137-
138-
cost := ctx.GasMeter().GasConsumed() - initialGas
139-
140-
if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) {
141-
return nil, vm.ErrOutOfGas
142-
}
143-
144-
if err := p.AddJournalEntries(stateDB, snapshot); err != nil {
145-
return nil, err
146-
}
147-
148-
return bz, nil
149-
})
95+
defer cmn.HandleGasError(ctx, contract, initialGas, &err)()
96+
97+
switch method.Name {
98+
// Custom transactions
99+
case ClaimRewardsMethod:
100+
bz, err = p.ClaimRewards(ctx, contract, stateDB, method, args)
101+
// Distribution transactions
102+
case SetWithdrawAddressMethod:
103+
bz, err = p.SetWithdrawAddress(ctx, contract, stateDB, method, args)
104+
case WithdrawDelegatorRewardMethod:
105+
bz, err = p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args)
106+
case WithdrawValidatorCommissionMethod:
107+
bz, err = p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args)
108+
case FundCommunityPoolMethod:
109+
bz, err = p.FundCommunityPool(ctx, contract, stateDB, method, args)
110+
case DepositValidatorRewardsPoolMethod:
111+
bz, err = p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args)
112+
// Distribution queries
113+
case ValidatorDistributionInfoMethod:
114+
bz, err = p.ValidatorDistributionInfo(ctx, contract, method, args)
115+
case ValidatorOutstandingRewardsMethod:
116+
bz, err = p.ValidatorOutstandingRewards(ctx, contract, method, args)
117+
case ValidatorCommissionMethod:
118+
bz, err = p.ValidatorCommission(ctx, contract, method, args)
119+
case ValidatorSlashesMethod:
120+
bz, err = p.ValidatorSlashes(ctx, contract, method, args)
121+
case DelegationRewardsMethod:
122+
bz, err = p.DelegationRewards(ctx, contract, method, args)
123+
case DelegationTotalRewardsMethod:
124+
bz, err = p.DelegationTotalRewards(ctx, contract, method, args)
125+
case DelegatorValidatorsMethod:
126+
bz, err = p.DelegatorValidators(ctx, contract, method, args)
127+
case DelegatorWithdrawAddressMethod:
128+
bz, err = p.DelegatorWithdrawAddress(ctx, contract, method, args)
129+
case CommunityPoolMethod:
130+
bz, err = p.CommunityPool(ctx, contract, method, args)
131+
}
132+
133+
if err != nil {
134+
return nil, err
135+
}
136+
137+
cost := ctx.GasMeter().GasConsumed() - initialGas
138+
139+
if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) {
140+
return nil, vm.ErrOutOfGas
141+
}
142+
143+
if err = p.AddJournalEntries(stateDB); err != nil {
144+
return nil, err
145+
}
146+
147+
return bz, nil
150148
}
151149

152150
// IsTransaction checks if the given method name corresponds to a transaction or query.

precompiles/erc20/erc20.go

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -144,31 +144,29 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [
144144
return nil, fmt.Errorf(ErrCannotReceiveFunds, contract.Value().String())
145145
}
146146

147-
ctx, stateDB, snapshot, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction)
147+
ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction)
148148
if err != nil {
149149
return nil, err
150150
}
151151

152152
// This handles any out of gas errors that may occur during the execution of a precompile tx or query.
153153
// It avoids panics and returns the out of gas error so the EVM can continue gracefully.
154-
defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)()
155-
156-
return p.RunAtomic(snapshot, stateDB, func() ([]byte, error) {
157-
bz, err = p.HandleMethod(ctx, contract, stateDB, method, args)
158-
if err != nil {
159-
return nil, err
160-
}
161-
162-
cost := ctx.GasMeter().GasConsumed() - initialGas
163-
164-
if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) {
165-
return nil, vm.ErrOutOfGas
166-
}
167-
if err := p.AddJournalEntries(stateDB, snapshot); err != nil {
168-
return nil, err
169-
}
170-
return bz, nil
171-
})
154+
defer cmn.HandleGasError(ctx, contract, initialGas, &err)()
155+
156+
bz, err = p.HandleMethod(ctx, contract, stateDB, method, args)
157+
if err != nil {
158+
return nil, err
159+
}
160+
161+
cost := ctx.GasMeter().GasConsumed() - initialGas
162+
163+
if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) {
164+
return nil, vm.ErrOutOfGas
165+
}
166+
if err = p.AddJournalEntries(stateDB); err != nil {
167+
return nil, err
168+
}
169+
return bz, nil
172170
}
173171

174172
// IsTransaction checks if the given method name corresponds to a transaction or query.

0 commit comments

Comments
 (0)