github.com/amazechain/amc@v0.1.3/internal/tracers/native/prestate.go (about) 1 // Copyright 2023 The AmazeChain Authors 2 // This file is part of the AmazeChain library. 3 // 4 // The AmazeChain library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The AmazeChain library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the AmazeChain library. If not, see <http://www.gnu.org/licenses/>. 16 17 package native 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "github.com/holiman/uint256" 23 "math/big" 24 "sync/atomic" 25 26 "github.com/amazechain/amc/common/crypto" 27 "github.com/amazechain/amc/common/hexutil" 28 common "github.com/amazechain/amc/common/types" 29 "github.com/amazechain/amc/internal/tracers" 30 "github.com/amazechain/amc/internal/vm" 31 ) 32 33 //go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go 34 35 func init() { 36 tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false) 37 } 38 39 type state = map[common.Address]*account 40 41 type account struct { 42 Balance *big.Int `json:"balance,omitempty"` 43 Code []byte `json:"code,omitempty"` 44 Nonce uint64 `json:"nonce,omitempty"` 45 Storage map[common.Hash]common.Hash `json:"storage,omitempty"` 46 } 47 48 func (a *account) exists() bool { 49 return a.Nonce > 0 || len(a.Code) > 0 || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0) 50 } 51 52 type accountMarshaling struct { 53 Balance *hexutil.Big 54 Code hexutil.Bytes 55 } 56 57 type prestateTracer struct { 58 noopTracer 59 env *vm.EVM 60 pre state 61 post state 62 create bool 63 to common.Address 64 gasLimit uint64 // Amount of gas bought for the whole tx 65 config prestateTracerConfig 66 interrupt uint32 // Atomic flag to signal execution interruption 67 reason error // Textual reason for the interruption 68 created map[common.Address]bool 69 deleted map[common.Address]bool 70 } 71 72 type prestateTracerConfig struct { 73 DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications 74 } 75 76 func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { 77 var config prestateTracerConfig 78 if cfg != nil { 79 if err := json.Unmarshal(cfg, &config); err != nil { 80 return nil, err 81 } 82 } 83 return &prestateTracer{ 84 pre: state{}, 85 post: state{}, 86 config: config, 87 created: make(map[common.Address]bool), 88 deleted: make(map[common.Address]bool), 89 }, nil 90 } 91 92 // CaptureStart implements the EVMLogger interface to initialize the tracing operation. 93 func (t *prestateTracer) CaptureStart(env vm.VMInterface, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *uint256.Int) { 94 t.env = env.(*vm.EVM) 95 t.create = create 96 t.to = to 97 98 t.lookupAccount(from) 99 t.lookupAccount(to) 100 t.lookupAccount(env.Context().Coinbase) 101 102 // The recipient balance includes the value transferred. 103 toBal := new(big.Int).Sub(t.pre[to].Balance, value.ToBig()) 104 t.pre[to].Balance = toBal 105 106 // The sender balance is after reducing: value and gasLimit. 107 // We need to re-add them to get the pre-tx balance. 108 fromBal := new(big.Int).Set(t.pre[from].Balance) 109 gasPrice := env.TxContext().GasPrice 110 consumedGas := new(big.Int).Mul(gasPrice.ToBig(), new(big.Int).SetUint64(t.gasLimit)) 111 fromBal.Add(fromBal, new(big.Int).Add(value.ToBig(), consumedGas)) 112 t.pre[from].Balance = fromBal 113 t.pre[from].Nonce-- 114 115 if create && t.config.DiffMode { 116 t.created[to] = true 117 } 118 } 119 120 // CaptureEnd is called after the call finishes to finalize the tracing. 121 func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { 122 if t.config.DiffMode { 123 return 124 } 125 126 if t.create { 127 // Keep existing account prior to contract creation at that address 128 if s := t.pre[t.to]; s != nil && !s.exists() { 129 // Exclude newly created contract. 130 delete(t.pre, t.to) 131 } 132 } 133 } 134 135 // CaptureState implements the EVMLogger interface to trace a single step of VM execution. 136 func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { 137 stack := scope.Stack 138 stackData := stack.Data 139 stackLen := len(stackData) 140 caller := scope.Contract.Address() 141 switch { 142 case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): 143 slot := common.Hash(stackData[stackLen-1].Bytes32()) 144 t.lookupStorage(caller, slot) 145 case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): 146 addr := common.Address(stackData[stackLen-1].Bytes20()) 147 t.lookupAccount(addr) 148 if op == vm.SELFDESTRUCT { 149 t.deleted[caller] = true 150 } 151 case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): 152 addr := common.Address(stackData[stackLen-2].Bytes20()) 153 t.lookupAccount(addr) 154 case op == vm.CREATE: 155 nonce := t.env.IntraBlockState().GetNonce(caller) 156 addr := crypto.CreateAddress(caller, nonce) 157 t.lookupAccount(addr) 158 t.created[addr] = true 159 case stackLen >= 4 && op == vm.CREATE2: 160 offset := stackData[stackLen-2] 161 size := stackData[stackLen-3] 162 init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) 163 inithash := crypto.Keccak256(init) 164 salt := stackData[stackLen-4] 165 addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) 166 t.lookupAccount(addr) 167 t.created[addr] = true 168 } 169 } 170 171 func (t *prestateTracer) CaptureTxStart(gasLimit uint64) { 172 t.gasLimit = gasLimit 173 } 174 175 func (t *prestateTracer) CaptureTxEnd(restGas uint64) { 176 if !t.config.DiffMode { 177 return 178 } 179 180 for addr, state := range t.pre { 181 // The deleted account's state is pruned from `post` but kept in `pre` 182 if _, ok := t.deleted[addr]; ok { 183 continue 184 } 185 modified := false 186 postAccount := &account{Storage: make(map[common.Hash]common.Hash)} 187 newBalance := t.env.IntraBlockState().GetBalance(addr) 188 newNonce := t.env.IntraBlockState().GetNonce(addr) 189 newCode := t.env.IntraBlockState().GetCode(addr) 190 191 if newBalance.ToBig().Cmp(t.pre[addr].Balance) != 0 { 192 modified = true 193 postAccount.Balance = newBalance.ToBig() 194 } 195 if newNonce != t.pre[addr].Nonce { 196 modified = true 197 postAccount.Nonce = newNonce 198 } 199 if !bytes.Equal(newCode, t.pre[addr].Code) { 200 modified = true 201 postAccount.Code = newCode 202 } 203 204 for key, val := range state.Storage { 205 // don't include the empty slot 206 if val == (common.Hash{}) { 207 delete(t.pre[addr].Storage, key) 208 } 209 210 var newVal uint256.Int 211 t.env.IntraBlockState().GetState(addr, &key, &newVal) 212 if val == newVal.Bytes32() { 213 // Omit unchanged slots 214 delete(t.pre[addr].Storage, key) 215 } else { 216 modified = true 217 if newVal.Bytes32() != (common.Hash{}) { 218 postAccount.Storage[key] = newVal.Bytes32() 219 } 220 } 221 } 222 223 if modified { 224 t.post[addr] = postAccount 225 } else { 226 // if state is not modified, then no need to include into the pre state 227 delete(t.pre, addr) 228 } 229 } 230 // the new created contracts' prestate were empty, so delete them 231 for a := range t.created { 232 // the created contract maybe exists in statedb before the creating tx 233 if s := t.pre[a]; s != nil && !s.exists() { 234 delete(t.pre, a) 235 } 236 } 237 } 238 239 // GetResult returns the json-encoded nested list of call traces, and any 240 // error arising from the encoding or forceful termination (via `Stop`). 241 func (t *prestateTracer) GetResult() (json.RawMessage, error) { 242 var res []byte 243 var err error 244 if t.config.DiffMode { 245 res, err = json.Marshal(struct { 246 Post state `json:"post"` 247 Pre state `json:"pre"` 248 }{t.post, t.pre}) 249 } else { 250 res, err = json.Marshal(t.pre) 251 } 252 if err != nil { 253 return nil, err 254 } 255 return json.RawMessage(res), t.reason 256 } 257 258 // Stop terminates execution of the tracer at the first opportune moment. 259 func (t *prestateTracer) Stop(err error) { 260 t.reason = err 261 atomic.StoreUint32(&t.interrupt, 1) 262 } 263 264 // lookupAccount fetches details of an account and adds it to the prestate 265 // if it doesn't exist there. 266 func (t *prestateTracer) lookupAccount(addr common.Address) { 267 if _, ok := t.pre[addr]; ok { 268 return 269 } 270 271 t.pre[addr] = &account{ 272 Balance: t.env.IntraBlockState().GetBalance(addr).ToBig(), 273 Nonce: t.env.IntraBlockState().GetNonce(addr), 274 Code: t.env.IntraBlockState().GetCode(addr), 275 Storage: make(map[common.Hash]common.Hash), 276 } 277 } 278 279 // lookupStorage fetches the requested storage slot and adds 280 // it to the prestate of the given contract. It assumes `lookupAccount` 281 // has been performed on the contract before. 282 func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) { 283 if _, ok := t.pre[addr].Storage[key]; ok { 284 return 285 } 286 var val uint256.Int 287 t.env.IntraBlockState().GetState(addr, &key, &val) 288 t.pre[addr].Storage[key] = val.Bytes32() 289 }