|
| 1 | +import |
| 2 | + std/[tables, math, times], |
| 3 | + eth/[keys], |
| 4 | + stew/byteutils, |
| 5 | + unittest2, |
| 6 | + ../nimbus/core/chain, |
| 7 | + ../nimbus/core/tx_pool, |
| 8 | + ../nimbus/core/casper, |
| 9 | + ../nimbus/db/accounts_cache, |
| 10 | + ../nimbus/utils/[eof, utils], |
| 11 | + ../nimbus/evm/interpreter/op_codes, |
| 12 | + ../nimbus/[common, config, transaction], |
| 13 | + ./test_txpool/helpers |
| 14 | + |
| 15 | +const |
| 16 | + baseDir = [".", "tests"] |
| 17 | + repoDir = [".", "customgenesis"] |
| 18 | + genesisFile = "eof.json" |
| 19 | + |
| 20 | +type |
| 21 | + TestEnv = object |
| 22 | + nonce : uint64 |
| 23 | + chainId : ChainId |
| 24 | + vaultKey: PrivateKey |
| 25 | + conf : NimbusConf |
| 26 | + com : CommonRef |
| 27 | + chain : ChainRef |
| 28 | + xp : TxPoolRef |
| 29 | + |
| 30 | +proc toAddress(x: string): EthAddress = |
| 31 | + hexToByteArray[20](x) |
| 32 | + |
| 33 | +proc privKey(keyHex: string): PrivateKey = |
| 34 | + let kRes = PrivateKey.fromHex(keyHex) |
| 35 | + if kRes.isErr: |
| 36 | + echo kRes.error |
| 37 | + quit(QuitFailure) |
| 38 | + |
| 39 | + kRes.get() |
| 40 | + |
| 41 | +proc toAddress(key: PrivateKey): EthAddress = |
| 42 | + let pubKey = key.toPublicKey |
| 43 | + pubKey.toCanonicalAddress |
| 44 | + |
| 45 | +func eth(n: int): UInt256 = |
| 46 | + n.u256 * pow(10.u256, 18) |
| 47 | + |
| 48 | +proc fm(input, output, max: int): FunctionMetadata = |
| 49 | + FunctionMetadata(input: input.uint8, |
| 50 | + output: output.uint8, maxStackHeight: max.uint16) |
| 51 | + |
| 52 | +const |
| 53 | + createDeployer = [ |
| 54 | + byte(CALLDATASIZE), # size |
| 55 | + byte(PUSH1), 0x00, # offset |
| 56 | + byte(PUSH1), 0x00, # dst |
| 57 | + byte(CALLDATACOPY), |
| 58 | + byte(CALLDATASIZE), # len |
| 59 | + byte(PUSH1), 0x00, # offset |
| 60 | + byte(PUSH1), 0x00, # value |
| 61 | + byte(CREATE), |
| 62 | + ] |
| 63 | + |
| 64 | + create2Deployer = [ |
| 65 | + byte(CALLDATASIZE), # len |
| 66 | + byte(PUSH1), 0x00, # offset |
| 67 | + byte(PUSH1), 0x00, # dst |
| 68 | + byte(CALLDATACOPY), |
| 69 | + byte(PUSH1), 0x00, # salt |
| 70 | + byte(CALLDATASIZE), # len |
| 71 | + byte(PUSH1), 0x00, # offset |
| 72 | + byte(PUSH1), 0x00, # value |
| 73 | + byte(CREATE2), |
| 74 | + ] |
| 75 | + |
| 76 | + aa = toAddress("0x000000000000000000000000000000000000aaaa") |
| 77 | + bb = toAddress("0x000000000000000000000000000000000000bbbb") |
| 78 | + cc = toAddress("0x000000000000000000000000000000000000cccc") |
| 79 | + funds = 1.eth |
| 80 | + vaultKeyHex = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" |
| 81 | + |
| 82 | +proc acc(code: openArray[byte]): GenesisAccount = |
| 83 | + GenesisAccount(code: @code) |
| 84 | + |
| 85 | +proc acc(balance: UInt256): GenesisAccount = |
| 86 | + GenesisAccount(balance: balance) |
| 87 | + |
| 88 | +proc makeCode(): seq[byte] = |
| 89 | + var c: Container |
| 90 | + c.types = @[ |
| 91 | + fm(0, 0, 0), |
| 92 | + fm(0, 0, 2), |
| 93 | + fm(0, 0, 0), |
| 94 | + fm(0, 0, 2) |
| 95 | + ] |
| 96 | + |
| 97 | + c.code = @[@[ |
| 98 | + byte(CALLF), |
| 99 | + byte(0), |
| 100 | + byte(1), |
| 101 | + byte(CALLF), |
| 102 | + byte(0), |
| 103 | + byte(2), |
| 104 | + byte(STOP), |
| 105 | + ], @[ |
| 106 | + byte(PUSH1), |
| 107 | + byte(2), |
| 108 | + byte(RJUMP), # skip first flag |
| 109 | + byte(0), |
| 110 | + byte(5), |
| 111 | + |
| 112 | + byte(PUSH1), |
| 113 | + byte(1), |
| 114 | + byte(PUSH1), |
| 115 | + byte(0), |
| 116 | + byte(SSTORE), # set first flag |
| 117 | + |
| 118 | + byte(PUSH1), |
| 119 | + byte(1), |
| 120 | + byte(SWAP1), |
| 121 | + byte(SUB), |
| 122 | + byte(DUP1), |
| 123 | + byte(RJUMPI), # jump to first flag, then don't branch |
| 124 | + byte(0xff), |
| 125 | + byte(0xF3), # -13 |
| 126 | + |
| 127 | + byte(PUSH1), |
| 128 | + byte(1), |
| 129 | + byte(PUSH1), |
| 130 | + byte(1), |
| 131 | + byte(SSTORE), # set second flag |
| 132 | + byte(RETF), |
| 133 | + ], @[ |
| 134 | + byte(PUSH1), |
| 135 | + byte(1), |
| 136 | + byte(PUSH1), |
| 137 | + byte(2), |
| 138 | + byte(SSTORE), # set third flag |
| 139 | + |
| 140 | + byte(CALLF), |
| 141 | + byte(0), |
| 142 | + byte(3), |
| 143 | + byte(RETF), |
| 144 | + ], @[ |
| 145 | + byte(PUSH1), |
| 146 | + byte(0), |
| 147 | + byte(RJUMPV), # jump over invalid op |
| 148 | + byte(1), |
| 149 | + byte(0), |
| 150 | + byte(1), |
| 151 | + |
| 152 | + byte(INVALID), |
| 153 | + |
| 154 | + byte(PUSH1), |
| 155 | + byte(1), |
| 156 | + byte(PUSH1), |
| 157 | + byte(3), |
| 158 | + byte(SSTORE), # set forth flag |
| 159 | + byte(RETF), |
| 160 | + ]] |
| 161 | + |
| 162 | + c.encode() |
| 163 | + |
| 164 | +proc preAlloc(address: EthAddress): GenesisAlloc = |
| 165 | + result[address] = acc(funds) |
| 166 | + result[bb] = acc(createDeployer) |
| 167 | + result[cc] = acc(create2Deployer) |
| 168 | + result[aa] = acc(makeCode()) |
| 169 | + |
| 170 | +proc initDeployCode(): seq[byte] = |
| 171 | + let c = Container( |
| 172 | + types: @[fm(0, 0, 0)], |
| 173 | + code : @[@[byte(STOP)]], |
| 174 | + ) |
| 175 | + c.encode() |
| 176 | + |
| 177 | +proc initInitCode(deployCode: openArray[byte]): seq[byte] = |
| 178 | + result = @[ |
| 179 | + byte(PUSH1), byte(deployCode.len), # len |
| 180 | + byte(PUSH1), 0x0c, # offset |
| 181 | + byte(PUSH1), 0x00, # dst offset |
| 182 | + byte(CODECOPY), |
| 183 | + |
| 184 | + # code in memory |
| 185 | + byte(PUSH1), byte(deployCode.len), # size |
| 186 | + byte(PUSH1), 0x00, # offset |
| 187 | + byte(RETURN), |
| 188 | + ] |
| 189 | + result.add deployCode |
| 190 | + |
| 191 | +func gwei(n: uint64): GasInt {.compileTime.} = |
| 192 | + GasInt(n * (10'u64 ^ 9'u64)) |
| 193 | + |
| 194 | +proc makeTx*(t: var TestEnv, |
| 195 | + recipient: Option[EthAddress], |
| 196 | + amount: UInt256, |
| 197 | + payload: openArray[byte] = []): Transaction = |
| 198 | + const |
| 199 | + gasLimit = 500000.GasInt |
| 200 | + gasFeeCap = 5.gwei |
| 201 | + gasTipCap = 2.GasInt |
| 202 | + |
| 203 | + let tx = Transaction( |
| 204 | + txType : TxEip1559, |
| 205 | + chainId : t.chainId, |
| 206 | + nonce : AccountNonce(t.nonce), |
| 207 | + gasLimit: gasLimit, |
| 208 | + maxPriorityFee: gasTipCap, |
| 209 | + maxFee : gasFeeCap, |
| 210 | + to : recipient, |
| 211 | + value : amount, |
| 212 | + payload : @payload |
| 213 | + ) |
| 214 | + |
| 215 | + inc t.nonce |
| 216 | + signTransaction(tx, t.vaultKey, t.chainId, eip155 = true) |
| 217 | + |
| 218 | +proc initEnv(): TestEnv = |
| 219 | + let |
| 220 | + signKey = privKey(vaultKeyHex) |
| 221 | + address = toAddress(signKey) |
| 222 | + |
| 223 | + var |
| 224 | + conf = makeConfig(@[ |
| 225 | + "--engine-signer:" & address.toHex, |
| 226 | + "--custom-network:" & genesisFile.findFilePath(baseDir,repoDir).value |
| 227 | + ]) |
| 228 | + |
| 229 | + conf.networkParams.genesis.alloc = preAlloc(address) |
| 230 | + |
| 231 | + let |
| 232 | + com = CommonRef.new( |
| 233 | + newMemoryDb(), |
| 234 | + conf.pruneMode == PruneMode.Full, |
| 235 | + conf.networkId, |
| 236 | + conf.networkParams |
| 237 | + ) |
| 238 | + chain = newChain(com) |
| 239 | + |
| 240 | + com.initializeEmptyDb() |
| 241 | + |
| 242 | + result = TestEnv( |
| 243 | + conf: conf, |
| 244 | + com: com, |
| 245 | + chain: chain, |
| 246 | + xp: TxPoolRef.new(com, conf.engineSigner), |
| 247 | + vaultKey: signKey, |
| 248 | + chainId: conf.networkParams.config.chainId, |
| 249 | + nonce: 0'u64 |
| 250 | + ) |
| 251 | + |
| 252 | +const |
| 253 | + prevRandao = EMPTY_UNCLE_HASH # it can be any valid hash |
| 254 | + |
| 255 | +proc eofMain*() = |
| 256 | + var |
| 257 | + env = initEnv() |
| 258 | + txs: seq[Transaction] |
| 259 | + stateRoot: Hash256 |
| 260 | + |
| 261 | + let |
| 262 | + deployCode = initDeployCode() |
| 263 | + initCode = initInitCode(deployCode) |
| 264 | + xp = env.xp |
| 265 | + com = env.com |
| 266 | + chain = env.chain |
| 267 | + |
| 268 | + # execute flag contract |
| 269 | + txs.add env.makeTx(some(aa), 0.u256) |
| 270 | + |
| 271 | + # deploy eof contract from eoa |
| 272 | + txs.add env.makeTx(none(EthAddress), 0.u256, initCode) |
| 273 | + |
| 274 | + # deploy eof contract from create contract |
| 275 | + txs.add env.makeTx(some(bb), 0.u256, initCode) |
| 276 | + |
| 277 | + # deploy eof contract from create2 contract |
| 278 | + txs.add env.makeTx(some(cc), 0.u256, initCode) |
| 279 | + |
| 280 | + suite "Test EOF code deployment": |
| 281 | + test "add txs to txpool": |
| 282 | + for tx in txs: |
| 283 | + let res = xp.addLocal(tx, force = true) |
| 284 | + check res.isOk |
| 285 | + if res.isErr: |
| 286 | + debugEcho res.error |
| 287 | + return |
| 288 | + |
| 289 | + # all txs accepted in txpool |
| 290 | + check xp.nItems.total == 4 |
| 291 | + |
| 292 | + test "generate POS block": |
| 293 | + com.pos.prevRandao = prevRandao |
| 294 | + com.pos.feeRecipient = aa |
| 295 | + com.pos.timestamp = getTime() |
| 296 | + |
| 297 | + let blk = xp.ethBlock() |
| 298 | + check com.isBlockAfterTtd(blk.header) |
| 299 | + |
| 300 | + let body = BlockBody( |
| 301 | + transactions: blk.txs, |
| 302 | + uncles: blk.uncles |
| 303 | + ) |
| 304 | + check blk.txs.len == 4 |
| 305 | + |
| 306 | + let rr = chain.persistBlocks([blk.header], [body]) |
| 307 | + check rr == ValidationResult.OK |
| 308 | + |
| 309 | + # save stateRoot for next test |
| 310 | + stateRoot = blk.header.stateRoot |
| 311 | + |
| 312 | + test "check flags and various deployment mechanisms": |
| 313 | + var state = AccountsCache.init( |
| 314 | + com.db.db, |
| 315 | + stateRoot, |
| 316 | + com.pruneTrie) |
| 317 | + |
| 318 | + # check flags |
| 319 | + for i in 0 ..< 4: |
| 320 | + let val = state.getStorage(aa, i.u256) |
| 321 | + check val == 1.u256 |
| 322 | + |
| 323 | + # deploy EOF with EOA |
| 324 | + let address = toAddress(env.vaultKey) |
| 325 | + var code = state.getCode(generateAddress(address, 1)) |
| 326 | + check code == deployCode |
| 327 | + |
| 328 | + # deploy EOF with CREATE |
| 329 | + code = state.getCode(generateAddress(bb, 0)) |
| 330 | + check code == deployCode |
| 331 | + |
| 332 | + # deploy EOF with CREATE2 |
| 333 | + let xx = generateSafeAddress(cc, ZERO_CONTRACTSALT, initCode) |
| 334 | + code = state.getCode(xx) |
| 335 | + check code == deployCode |
| 336 | + |
| 337 | +when isMainModule: |
| 338 | + eofMain() |
0 commit comments