github.com/onflow/flow-go@v0.33.17/fvm/evm/emulator/emulator.go (about) 1 package emulator 2 3 import ( 4 "math/big" 5 6 gethCommon "github.com/ethereum/go-ethereum/common" 7 gethCore "github.com/ethereum/go-ethereum/core" 8 gethTypes "github.com/ethereum/go-ethereum/core/types" 9 gethVM "github.com/ethereum/go-ethereum/core/vm" 10 gethCrypto "github.com/ethereum/go-ethereum/crypto" 11 "github.com/onflow/atree" 12 13 "github.com/onflow/flow-go/fvm/evm/emulator/state" 14 "github.com/onflow/flow-go/fvm/evm/types" 15 "github.com/onflow/flow-go/model/flow" 16 ) 17 18 // Emulator handles operations against evm runtime 19 type Emulator struct { 20 rootAddr flow.Address 21 ledger atree.Ledger 22 } 23 24 var _ types.Emulator = &Emulator{} 25 26 // NewEmulator constructs a new EVM Emulator 27 func NewEmulator( 28 ledger atree.Ledger, 29 rootAddr flow.Address, 30 ) *Emulator { 31 return &Emulator{ 32 rootAddr: rootAddr, 33 ledger: ledger, 34 } 35 } 36 37 func newConfig(ctx types.BlockContext) *Config { 38 return NewConfig( 39 WithBlockNumber(new(big.Int).SetUint64(ctx.BlockNumber)), 40 WithCoinbase(ctx.GasFeeCollector.ToCommon()), 41 WithDirectCallBaseGasUsage(ctx.DirectCallBaseGasUsage), 42 ) 43 } 44 45 // NewReadOnlyBlockView constructs a new readonly block view 46 func (em *Emulator) NewReadOnlyBlockView(ctx types.BlockContext) (types.ReadOnlyBlockView, error) { 47 execState, err := state.NewStateDB(em.ledger, em.rootAddr) 48 return &ReadOnlyBlockView{ 49 state: execState, 50 }, err 51 } 52 53 // NewBlockView constructs a new block view (mutable) 54 func (em *Emulator) NewBlockView(ctx types.BlockContext) (types.BlockView, error) { 55 cfg := newConfig(ctx) 56 return &BlockView{ 57 config: cfg, 58 rootAddr: em.rootAddr, 59 ledger: em.ledger, 60 }, nil 61 } 62 63 // ReadOnlyBlockView provides a read only view of a block 64 // could be used multiple times for queries 65 type ReadOnlyBlockView struct { 66 state types.StateDB 67 } 68 69 // BalanceOf returns the balance of the given address 70 func (bv *ReadOnlyBlockView) BalanceOf(address types.Address) (*big.Int, error) { 71 return bv.state.GetBalance(address.ToCommon()), nil 72 } 73 74 // CodeOf returns the code of the given address 75 func (bv *ReadOnlyBlockView) CodeOf(address types.Address) (types.Code, error) { 76 return bv.state.GetCode(address.ToCommon()), nil 77 } 78 79 // NonceOf returns the nonce of the given address 80 func (bv *ReadOnlyBlockView) NonceOf(address types.Address) (uint64, error) { 81 return bv.state.GetNonce(address.ToCommon()), nil 82 } 83 84 // BlockView allows mutation of the evm state as part of a block 85 // 86 // TODO: allow multiple calls per block view 87 // TODO: add block level commit (separation of trie commit to storage) 88 type BlockView struct { 89 config *Config 90 rootAddr flow.Address 91 ledger atree.Ledger 92 } 93 94 // DirectCall executes a direct call 95 func (bl *BlockView) DirectCall(call *types.DirectCall) (*types.Result, error) { 96 proc, err := bl.newProcedure() 97 if err != nil { 98 return nil, err 99 } 100 var res *types.Result 101 switch call.SubType { 102 case types.DepositCallSubType: 103 res, err = proc.mintTo(call.To, call.Value) 104 case types.WithdrawCallSubType: 105 res, err = proc.withdrawFrom(call.From, call.Value) 106 default: 107 res, err = proc.run(call.Message(), types.DirectCallTxType) 108 } 109 return res, err 110 } 111 112 // RunTransaction runs an evm transaction 113 func (bl *BlockView) RunTransaction( 114 tx *gethTypes.Transaction, 115 ) (*types.Result, error) { 116 var err error 117 proc, err := bl.newProcedure() 118 if err != nil { 119 return nil, err 120 } 121 122 msg, err := gethCore.TransactionToMessage(tx, GetSigner(bl.config), proc.config.BlockContext.BaseFee) 123 if err != nil { 124 // note that this is not a fatal error (e.g. due to bad signature) 125 // not a valid transaction 126 return nil, types.NewEVMValidationError(err) 127 } 128 129 // update tx context origin 130 proc.evm.TxContext.Origin = msg.From 131 res, err := proc.run(msg, tx.Type()) 132 return res, err 133 } 134 135 func (bl *BlockView) newProcedure() (*procedure, error) { 136 execState, err := state.NewStateDB(bl.ledger, bl.rootAddr) 137 if err != nil { 138 return nil, err 139 } 140 cfg := bl.config 141 return &procedure{ 142 config: cfg, 143 evm: gethVM.NewEVM( 144 *cfg.BlockContext, 145 *cfg.TxContext, 146 execState, 147 cfg.ChainConfig, 148 cfg.EVMConfig, 149 ), 150 state: execState, 151 }, nil 152 } 153 154 type procedure struct { 155 config *Config 156 evm *gethVM.EVM 157 state types.StateDB 158 } 159 160 // commit commits the changes to the state. 161 func (proc *procedure) commit() error { 162 return handleCommitError(proc.state.Commit()) 163 } 164 165 func handleCommitError(err error) error { 166 if err == nil { 167 return nil 168 } 169 // if known types (state errors) don't do anything and return 170 if types.IsAFatalError(err) || types.IsAStateError(err) { 171 return err 172 } 173 174 // else is a new fatal error 175 return types.NewFatalError(err) 176 } 177 178 func (proc *procedure) mintTo(address types.Address, amount *big.Int) (*types.Result, error) { 179 addr := address.ToCommon() 180 res := &types.Result{ 181 GasConsumed: proc.config.DirectCallBaseGasUsage, 182 TxType: types.DirectCallTxType, 183 } 184 185 // create account if not exist 186 if !proc.state.Exist(addr) { 187 proc.state.CreateAccount(addr) 188 } 189 190 // add balance 191 proc.state.AddBalance(addr, amount) 192 193 // we don't need to increment any nonce, given the origin doesn't exist 194 return res, proc.commit() 195 } 196 197 func (proc *procedure) withdrawFrom(address types.Address, amount *big.Int) (*types.Result, error) { 198 199 addr := address.ToCommon() 200 res := &types.Result{ 201 GasConsumed: proc.config.DirectCallBaseGasUsage, 202 TxType: types.DirectCallTxType, 203 } 204 205 // check if account exists 206 // while this method is only called from bridged accounts 207 // it might be the case that someone creates a bridged account 208 // and never transfer tokens to and call for withdraw 209 // TODO: we might revisit this apporach and 210 // return res, types.ErrAccountDoesNotExist 211 // instead 212 if !proc.state.Exist(addr) { 213 proc.state.CreateAccount(addr) 214 } 215 216 // check the source account balance 217 // if balance is lower than amount needed for withdrawal, error out 218 if proc.state.GetBalance(addr).Cmp(amount) < 0 { 219 return res, types.ErrInsufficientBalance 220 } 221 222 // sub balance 223 proc.state.SubBalance(addr, amount) 224 225 // we increment the nonce for source account cause 226 // withdraw counts as a transaction 227 nonce := proc.state.GetNonce(addr) 228 proc.state.SetNonce(addr, nonce+1) 229 230 return res, proc.commit() 231 } 232 233 func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, error) { 234 res := types.Result{ 235 TxType: txType, 236 } 237 238 gasPool := (*gethCore.GasPool)(&proc.config.BlockContext.GasLimit) 239 execResult, err := gethCore.NewStateTransition( 240 proc.evm, 241 msg, 242 gasPool, 243 ).TransitionDb() 244 if err != nil { 245 res.Failed = true 246 // if the error is a fatal error or a non-fatal state error return it 247 if types.IsAFatalError(err) || types.IsAStateError(err) { 248 return &res, err 249 } 250 // otherwise is a validation error (pre-check failure) 251 // no state change, wrap the error and return 252 return &res, types.NewEVMValidationError(err) 253 } 254 255 // if prechecks are passed, the exec result won't be nil 256 if execResult != nil { 257 res.GasConsumed = execResult.UsedGas 258 if !execResult.Failed() { // collect vm errors 259 res.ReturnedValue = execResult.ReturnData 260 // If the transaction created a contract, store the creation address in the receipt. 261 if msg.To == nil { 262 res.DeployedContractAddress = types.NewAddress(gethCrypto.CreateAddress(msg.From, msg.Nonce)) 263 } 264 res.Logs = proc.state.Logs( 265 // TODO pass proper hash values 266 gethCommon.Hash{}, 267 proc.config.BlockContext.BlockNumber.Uint64(), 268 gethCommon.Hash{}, 269 0, 270 ) 271 } else { 272 res.Failed = true 273 err = types.NewEVMExecutionError(execResult.Err) 274 } 275 } 276 commitErr := proc.commit() 277 if commitErr != nil { 278 return &res, commitErr 279 } 280 return &res, err 281 }