github.com/onflow/flow-go@v0.33.17/fvm/evm/handler/handler.go (about) 1 package handler 2 3 import ( 4 "bytes" 5 6 gethTypes "github.com/ethereum/go-ethereum/core/types" 7 "github.com/ethereum/go-ethereum/rlp" 8 "github.com/onflow/cadence/runtime/common" 9 10 "github.com/onflow/flow-go/fvm/environment" 11 "github.com/onflow/flow-go/fvm/errors" 12 "github.com/onflow/flow-go/fvm/evm/types" 13 ) 14 15 // ContractHandler is responsible for triggering calls to emulator, metering, 16 // event emission and updating the block 17 // 18 // TODO and Warning: currently database keeps a copy of roothash, and if after 19 // commiting the changes by the evm we want to revert in this code we need to reset that 20 // or we should always do all the checks and return before calling the emulator, 21 // after that should be only event emissions and computation usage updates. 22 // thats another reason we first check the computation limit before using. 23 // in the future we might benefit from a view style of access to db passed as 24 // a param to the emulator. 25 type ContractHandler struct { 26 flowTokenAddress common.Address 27 blockstore types.BlockStore 28 addressAllocator types.AddressAllocator 29 backend types.Backend 30 emulator types.Emulator 31 } 32 33 func (h *ContractHandler) FlowTokenAddress() common.Address { 34 return h.flowTokenAddress 35 } 36 37 var _ types.ContractHandler = &ContractHandler{} 38 39 func NewContractHandler( 40 flowTokenAddress common.Address, 41 blockstore types.BlockStore, 42 addressAllocator types.AddressAllocator, 43 backend types.Backend, 44 emulator types.Emulator, 45 ) *ContractHandler { 46 return &ContractHandler{ 47 flowTokenAddress: flowTokenAddress, 48 blockstore: blockstore, 49 addressAllocator: addressAllocator, 50 backend: backend, 51 emulator: emulator, 52 } 53 } 54 55 // AllocateAddress allocates an address to be used by the bridged accounts 56 func (h *ContractHandler) AllocateAddress() types.Address { 57 target, err := h.addressAllocator.AllocateAddress() 58 handleError(err) 59 return target 60 } 61 62 // AccountByAddress returns the account for the given address, 63 // if isAuthorized is set, account is controlled by the FVM (bridged accounts) 64 func (h *ContractHandler) AccountByAddress(addr types.Address, isAuthorized bool) types.Account { 65 return newAccount(h, addr, isAuthorized) 66 } 67 68 // LastExecutedBlock returns the last executed block 69 func (h *ContractHandler) LastExecutedBlock() *types.Block { 70 block, err := h.blockstore.LatestBlock() 71 handleError(err) 72 return block 73 } 74 75 // Run runs an rlpencoded evm transaction and 76 // collects the gas fees and pay it to the coinbase address provided. 77 func (h *ContractHandler) Run(rlpEncodedTx []byte, coinbase types.Address) { 78 // step 1 - transaction decoding 79 encodedLen := uint(len(rlpEncodedTx)) 80 err := h.backend.MeterComputation(environment.ComputationKindRLPDecoding, encodedLen) 81 handleError(err) 82 83 tx := gethTypes.Transaction{} 84 err = tx.DecodeRLP( 85 rlp.NewStream( 86 bytes.NewReader(rlpEncodedTx), 87 uint64(encodedLen))) 88 handleError(err) 89 90 // step 2 - run transaction 91 h.checkGasLimit(types.GasLimit(tx.Gas())) 92 93 ctx := h.getBlockContext() 94 ctx.GasFeeCollector = coinbase 95 blk, err := h.emulator.NewBlockView(ctx) 96 handleError(err) 97 98 res, err := blk.RunTransaction(&tx) 99 h.meterGasUsage(res) 100 handleError(err) 101 102 // step 3 - update block proposal 103 bp, err := h.blockstore.BlockProposal() 104 handleError(err) 105 106 txHash := tx.Hash() 107 bp.AppendTxHash(txHash) 108 109 // step 4 - emit events 110 h.emitEvent(types.NewTransactionExecutedEvent( 111 bp.Height, 112 rlpEncodedTx, 113 txHash, 114 res, 115 )) 116 h.emitEvent(types.NewBlockExecutedEvent(bp)) 117 118 // step 5 - commit block proposal 119 err = h.blockstore.CommitBlockProposal() 120 handleError(err) 121 } 122 123 func (h *ContractHandler) checkGasLimit(limit types.GasLimit) { 124 // check gas limit against what has been left on the transaction side 125 if !h.backend.ComputationAvailable(environment.ComputationKindEVMGasUsage, uint(limit)) { 126 handleError(types.ErrInsufficientComputation) 127 } 128 } 129 130 func (h *ContractHandler) meterGasUsage(res *types.Result) { 131 if res != nil { 132 err := h.backend.MeterComputation(environment.ComputationKindEVMGasUsage, uint(res.GasConsumed)) 133 handleError(err) 134 } 135 } 136 137 func (h *ContractHandler) emitEvent(event *types.Event) { 138 ev, err := event.Payload.CadenceEvent() 139 handleError(err) 140 141 err = h.backend.EmitEvent(ev) 142 handleError(err) 143 } 144 145 func (h *ContractHandler) getBlockContext() types.BlockContext { 146 bp, err := h.blockstore.BlockProposal() 147 handleError(err) 148 return types.BlockContext{ 149 BlockNumber: bp.Height, 150 DirectCallBaseGasUsage: types.DefaultDirectCallBaseGasUsage, 151 } 152 } 153 154 type Account struct { 155 isAuthorized bool 156 address types.Address 157 fch *ContractHandler 158 } 159 160 // newAccount creates a new evm account 161 func newAccount(fch *ContractHandler, addr types.Address, isAuthorized bool) *Account { 162 return &Account{ 163 isAuthorized: isAuthorized, 164 fch: fch, 165 address: addr, 166 } 167 } 168 169 // Address returns the address associated with the bridged account 170 func (a *Account) Address() types.Address { 171 return a.address 172 } 173 174 // Balance returns the balance of this bridged account 175 // 176 // TODO: we might need to meter computation for read only operations as well 177 // currently the storage limits is enforced 178 func (a *Account) Balance() types.Balance { 179 ctx := a.fch.getBlockContext() 180 181 blk, err := a.fch.emulator.NewReadOnlyBlockView(ctx) 182 handleError(err) 183 184 bl, err := blk.BalanceOf(a.address) 185 handleError(err) 186 187 balance, err := types.NewBalanceFromAttoFlow(bl) 188 handleError(err) 189 return balance 190 } 191 192 // Deposit deposits the token from the given vault into the flow evm main vault 193 // and update the account balance with the new amount 194 func (a *Account) Deposit(v *types.FLOWTokenVault) { 195 cfg := a.fch.getBlockContext() 196 a.fch.checkGasLimit(types.GasLimit(cfg.DirectCallBaseGasUsage)) 197 198 call := types.NewDepositCall( 199 a.address, 200 v.Balance().ToAttoFlow(), 201 ) 202 a.executeAndHandleCall(a.fch.getBlockContext(), call, v.Balance().ToAttoFlow().Uint64(), false) 203 } 204 205 // Withdraw deducts the balance from the account and 206 // withdraw and return flow token from the Flex main vault. 207 func (a *Account) Withdraw(b types.Balance) *types.FLOWTokenVault { 208 a.checkAuthorized() 209 210 cfg := a.fch.getBlockContext() 211 a.fch.checkGasLimit(types.GasLimit(cfg.DirectCallBaseGasUsage)) 212 213 // check balance of flex vault 214 bp, err := a.fch.blockstore.BlockProposal() 215 handleError(err) 216 if b.ToAttoFlow().Uint64() > bp.TotalSupply { 217 handleError(types.ErrInsufficientTotalSupply) 218 } 219 220 call := types.NewWithdrawCall( 221 a.address, 222 b.ToAttoFlow(), 223 ) 224 a.executeAndHandleCall(a.fch.getBlockContext(), call, b.ToAttoFlow().Uint64(), true) 225 226 return types.NewFlowTokenVault(b) 227 } 228 229 // Transfer transfers tokens between accounts 230 func (a *Account) Transfer(to types.Address, balance types.Balance) { 231 a.checkAuthorized() 232 233 ctx := a.fch.getBlockContext() 234 a.fch.checkGasLimit(types.GasLimit(ctx.DirectCallBaseGasUsage)) 235 236 call := types.NewTransferCall( 237 a.address, 238 to, 239 balance.ToAttoFlow(), 240 ) 241 a.executeAndHandleCall(ctx, call, 0, false) 242 } 243 244 // Deploy deploys a contract to the EVM environment 245 // the new deployed contract would be at the returned address and 246 // the contract data is not controlled by the caller accounts 247 func (a *Account) Deploy(code types.Code, gaslimit types.GasLimit, balance types.Balance) types.Address { 248 a.checkAuthorized() 249 a.fch.checkGasLimit(gaslimit) 250 251 call := types.NewDeployCall( 252 a.address, 253 code, 254 uint64(gaslimit), 255 balance.ToAttoFlow(), 256 ) 257 res := a.executeAndHandleCall(a.fch.getBlockContext(), call, 0, false) 258 return types.Address(res.DeployedContractAddress) 259 } 260 261 // Call calls a smart contract function with the given data 262 // it would limit the gas used according to the limit provided 263 // given it doesn't goes beyond what Flow transaction allows. 264 // the balance would be deducted from the OFA account and would be transferred to the target address 265 func (a *Account) Call(to types.Address, data types.Data, gaslimit types.GasLimit, balance types.Balance) types.Data { 266 a.checkAuthorized() 267 a.fch.checkGasLimit(gaslimit) 268 call := types.NewContractCall( 269 a.address, 270 to, 271 data, 272 uint64(gaslimit), 273 balance.ToAttoFlow(), 274 ) 275 res := a.executeAndHandleCall(a.fch.getBlockContext(), call, 0, false) 276 return res.ReturnedValue 277 } 278 279 func (a *Account) executeAndHandleCall( 280 ctx types.BlockContext, 281 call *types.DirectCall, 282 totalSupplyDiff uint64, 283 deductSupplyDiff bool, 284 ) *types.Result { 285 // execute the call 286 blk, err := a.fch.emulator.NewBlockView(ctx) 287 handleError(err) 288 289 res, err := blk.DirectCall(call) 290 a.fch.meterGasUsage(res) 291 handleError(err) 292 293 // update block proposal 294 callHash, err := call.Hash() 295 if err != nil { 296 err = types.NewFatalError(err) 297 handleError(err) 298 } 299 300 bp, err := a.fch.blockstore.BlockProposal() 301 handleError(err) 302 bp.AppendTxHash(callHash) 303 if deductSupplyDiff { 304 bp.TotalSupply -= totalSupplyDiff 305 } else { 306 // TODO: add overflow errors (even though we might never get there) 307 bp.TotalSupply += totalSupplyDiff 308 } 309 310 // emit events 311 encoded, err := call.Encode() 312 handleError(err) 313 314 a.fch.emitEvent( 315 types.NewTransactionExecutedEvent( 316 bp.Height, 317 encoded, 318 callHash, 319 res, 320 ), 321 ) 322 a.fch.emitEvent(types.NewBlockExecutedEvent(bp)) 323 324 // commit block proposal 325 err = a.fch.blockstore.CommitBlockProposal() 326 handleError(err) 327 328 return res 329 } 330 331 func (a *Account) checkAuthorized() { 332 // check if account is authorized (i.e. is a bridged account) 333 if !a.isAuthorized { 334 handleError(types.ErrUnAuthroizedMethodCall) 335 } 336 } 337 338 func handleError(err error) { 339 if err == nil { 340 return 341 } 342 343 if types.IsAFatalError(err) { 344 // don't wrap it 345 panic(err) 346 } 347 panic(errors.NewEVMError(err)) 348 }