diff --git a/Cargo.toml b/Cargo.toml index 5ddf6ec..8e90c8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "evm" -version = "0.1.0" +version = "0.0.1" edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +authors = ["arnaucube "] +license = "GPL-3.0" +repository = "https://github.com/arnaucube/evm-rs" [dependencies] num-bigint = "0.4" diff --git a/README.md b/README.md index fa73c20..b54555c 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,5 @@ EVM ([Ethereum Virtual Machine](https://ethereum.org/en/developers/docs/evm/)) implementation from scratch in Rust. *This is a repo done to get familiar with the EVM, do not use.* + +Opcodes implemented: 83/130. diff --git a/src/lib.rs b/src/lib.rs index 8dc9431..0d7d937 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,12 +3,15 @@ use num_bigint::BigUint; use std::collections::HashMap; pub mod opcodes; +pub mod u256; #[derive(Default)] pub struct Stack { pub pc: usize, pub calldata_i: usize, + pub calldata_size: usize, pub stack: Vec<[u8; 32]>, + pub storage: HashMap<[u8; 32], Vec>, pub mem: Vec, pub gas: u64, pub opcodes: HashMap, @@ -19,7 +22,9 @@ impl Stack { let mut s = Stack { pc: 0, calldata_i: 0, + calldata_size: 32, stack: Vec::new(), + storage: HashMap::new(), mem: Vec::new(), gas: 10000000000, opcodes: HashMap::new(), @@ -28,8 +33,28 @@ impl Stack { s } pub fn print_stack(&self) { + println!("stack ({}):", self.stack.len()); for i in (0..self.stack.len()).rev() { - println!("{:x?}", &self.stack[i][28..]); + // println!("{:x}", &self.stack[i][28..]); + println!("{:?}", vec_u8_to_hex(self.stack[i].to_vec())); + } + } + pub fn print_memory(&self) { + if self.mem.len() > 0 { + println!("memory ({}):", self.mem.len()); + println!("{:?}", vec_u8_to_hex(self.mem.to_vec())); + } + } + pub fn print_storage(&self) { + if self.storage.len() > 0 { + println!("storage ({}):", self.storage.len()); + for (key, value) in self.storage.iter() { + println!( + "{:?}: {:?}", + vec_u8_to_hex(key.to_vec()), + vec_u8_to_hex(value.to_vec()) + ); + } } } pub fn push(&mut self, b: [u8; 32]) { @@ -47,14 +72,14 @@ impl Stack { pub fn put_arbitrary(&mut self, b: &[u8]) { // TODO if b.len()>32 return error let mut d: [u8; 32] = [0; 32]; - d[32 - b.len()..].copy_from_slice(b); + d[0..b.len()].copy_from_slice(b); // put without left padding let l = self.stack.len(); self.stack[l - 1] = d; } pub fn pop(&mut self) -> [u8; 32] { match self.stack.pop() { Some(x) => x, - None => panic!("err"), + None => panic!("pop err"), } } @@ -71,13 +96,15 @@ impl Stack { if debug { println!( - "{:?} (0x{:x}): pc={:?} gas={:?}\nstack:", + "{} (0x{:x}): pc={:?} gas={:?}", self.opcodes.get(&opcode).unwrap().name, opcode, self.pc, self.gas, ); self.print_stack(); + self.print_memory(); + self.print_storage(); println!(); } match opcode & 0xf0 { @@ -85,6 +112,7 @@ impl Stack { // arithmetic match opcode { 0x00 => { + println!("0x00: STOP"); return Vec::new(); } 0x01 => self.add(), @@ -104,9 +132,9 @@ impl Stack { } 0x30 => { match opcode { - 0x35 => { - self.calldata_load(&calldata); - } + 0x35 => self.calldata_load(&calldata), + 0x36 => self.calldata_size(&calldata), + 0x39 => self.code_copy(&code), _ => panic!("unimplemented {:x}", opcode), } self.pc += 1; @@ -116,6 +144,7 @@ impl Stack { match opcode { 0x51 => self.mload(), 0x52 => self.mstore(), + 0x55 => self.sstore(), 0x56 => self.jump(), 0x57 => self.jump_i(), 0x5b => self.jump_dest(), @@ -152,13 +181,13 @@ impl Stack { } 0xf0 => { if opcode == 0xf3 { - let pos_to_return = u256_to_u64(self.pop()) as usize; - let len_to_return = u256_to_u64(self.pop()) as usize; + let pos_to_return = u256::u256_to_u64(self.pop()) as usize; + let len_to_return = u256::u256_to_u64(self.pop()) as usize; return self.mem[pos_to_return..pos_to_return + len_to_return].to_vec(); } } _ => { - panic!("unimplemented {:x}", opcode); + return panic!("unimplemented {:x}", opcode); } } self.gas -= self.opcodes.get(&opcode).unwrap().gas; @@ -166,15 +195,7 @@ impl Stack { Vec::new() } } - -pub fn u256_to_u64(a: [u8; 32]) -> u64 { - let mut b8: [u8; 8] = [0; 8]; - b8.copy_from_slice(&a[32 - 8..32]); - u64::from_be_bytes(b8) -} -pub fn str_to_u256(s: &str) -> [u8; 32] { - let bi = s.parse::().unwrap().to_bytes_be(); - let mut r: [u8; 32] = [0; 32]; - r[32 - bi.len()..].copy_from_slice(&bi[..]); - r +pub fn vec_u8_to_hex(bytes: Vec) -> String { + let strs: Vec = bytes.iter().map(|b| format!("{:02X}", b)).collect(); + strs.join("") } diff --git a/src/opcodes.rs b/src/opcodes.rs index eefe602..5e0b4c3 100644 --- a/src/opcodes.rs +++ b/src/opcodes.rs @@ -9,6 +9,7 @@ const GSTORAGEKILL: usize = 5000; const GSTORAGEMOD: usize = 5000; const GSTORAGEADD: usize = 20000; const GEXPONENTBYTE: usize = 10; // cost of EXP exponent per byte +const EXP_SUPPLEMENTAL_GAS: usize = 40; const GCOPY: usize = 3; // cost to copy one 32 byte word const GCONTRACTBYTE: usize = 200; // one byte of code in contract creation const GCALLVALUETRANSFER: usize = 9000; // non-zero-valued call @@ -57,81 +58,81 @@ pub fn new_opcodes() -> HashMap { opcodes.insert(0x02, new_opcode("MUL", 2, 1, 5)); opcodes.insert(0x03, new_opcode("SUB", 2, 1, 3)); opcodes.insert(0x04, new_opcode("DIV", 2, 1, 5)); - opcodes.insert(0x05, new_opcode("SDIV", 2, 1, 5)); + // opcodes.insert(0x05, new_opcode("SDIV", 2, 1, 5)); opcodes.insert(0x06, new_opcode("MOD", 2, 1, 5)); - opcodes.insert(0x07, new_opcode("SMOD", 2, 1, 5)); + // opcodes.insert(0x07, new_opcode("SMOD", 2, 1, 5)); opcodes.insert(0x08, new_opcode("ADDMOD", 3, 1, 8)); opcodes.insert(0x09, new_opcode("MULMOD", 3, 1, 8)); opcodes.insert(0x0a, new_opcode("EXP", 2, 1, 10)); - opcodes.insert(0x0b, new_opcode("SIGNEXTEND", 2, 1, 5)); + // opcodes.insert(0x0b, new_opcode("SIGNEXTEND", 2, 1, 5)); // boolean - opcodes.insert(0x10, new_opcode("LT", 2, 1, 3)); - opcodes.insert(0x11, new_opcode("GT", 2, 1, 3)); - opcodes.insert(0x12, new_opcode("SLT", 2, 1, 3)); - opcodes.insert(0x13, new_opcode("SGT", 2, 1, 3)); - opcodes.insert(0x14, new_opcode("EQ", 2, 1, 3)); - opcodes.insert(0x15, new_opcode("ISZERO", 1, 1, 3)); - opcodes.insert(0x16, new_opcode("AND", 2, 1, 3)); - opcodes.insert(0x17, new_opcode("OR", 2, 1, 3)); - opcodes.insert(0x18, new_opcode("XOR", 2, 1, 3)); - opcodes.insert(0x19, new_opcode("NOT", 1, 1, 3)); - opcodes.insert(0x1a, new_opcode("BYTE", 2, 1, 3)); + // opcodes.insert(0x10, new_opcode("LT", 2, 1, 3)); + // opcodes.insert(0x11, new_opcode("GT", 2, 1, 3)); + // opcodes.insert(0x12, new_opcode("SLT", 2, 1, 3)); + // opcodes.insert(0x13, new_opcode("SGT", 2, 1, 3)); + // opcodes.insert(0x14, new_opcode("EQ", 2, 1, 3)); + // opcodes.insert(0x15, new_opcode("ISZERO", 1, 1, 3)); + // opcodes.insert(0x16, new_opcode("AND", 2, 1, 3)); + // opcodes.insert(0x17, new_opcode("OR", 2, 1, 3)); + // opcodes.insert(0x18, new_opcode("XOR", 2, 1, 3)); + // opcodes.insert(0x19, new_opcode("NOT", 1, 1, 3)); + // opcodes.insert(0x1a, new_opcode("BYTE", 2, 1, 3)); // crypto - opcodes.insert(0x20, new_opcode("SHA3", 2, 1, 30)); + // opcodes.insert(0x20, new_opcode("SHA3", 2, 1, 30)); // contract context - opcodes.insert(0x30, new_opcode("ADDRESS", 0, 1, 2)); - opcodes.insert(0x31, new_opcode("BALANCE", 1, 1, 20)); - opcodes.insert(0x32, new_opcode("ORIGIN", 0, 1, 2)); - opcodes.insert(0x33, new_opcode("CALLER", 0, 1, 2)); - opcodes.insert(0x34, new_opcode("CALLVALUE", 0, 1, 2)); + // opcodes.insert(0x30, new_opcode("ADDRESS", 0, 1, 2)); + // opcodes.insert(0x31, new_opcode("BALANCE", 1, 1, 20)); + // opcodes.insert(0x32, new_opcode("ORIGIN", 0, 1, 2)); + // opcodes.insert(0x33, new_opcode("CALLER", 0, 1, 2)); + // opcodes.insert(0x34, new_opcode("CALLVALUE", 0, 1, 2)); opcodes.insert(0x35, new_opcode("CALLDATALOAD", 1, 1, 3)); opcodes.insert(0x36, new_opcode("CALLDATASIZE", 0, 1, 2)); - opcodes.insert(0x37, new_opcode("CALLDATACOPY", 3, 0, 3)); - opcodes.insert(0x38, new_opcode("CODESIZE", 0, 1, 2)); + // opcodes.insert(0x37, new_opcode("CALLDATACOPY", 3, 0, 3)); + // opcodes.insert(0x38, new_opcode("CODESIZE", 0, 1, 2)); opcodes.insert(0x39, new_opcode("CODECOPY", 3, 0, 3)); - opcodes.insert(0x3a, new_opcode("GASPRICE", 0, 1, 2)); - opcodes.insert(0x3b, new_opcode("EXTCODESIZE", 1, 1, 20)); - opcodes.insert(0x3c, new_opcode("EXTCODECOPY", 4, 0, 20)); + // opcodes.insert(0x3a, new_opcode("GASPRICE", 0, 1, 2)); + // opcodes.insert(0x3b, new_opcode("EXTCODESIZE", 1, 1, 20)); + // opcodes.insert(0x3c, new_opcode("EXTCODECOPY", 4, 0, 20)); // blockchain context - opcodes.insert(0x40, new_opcode("BLOCKHASH", 1, 1, 20)); - opcodes.insert(0x41, new_opcode("COINBASE", 0, 1, 2)); - opcodes.insert(0x42, new_opcode("TIMESTAMP", 0, 1, 2)); - opcodes.insert(0x43, new_opcode("NUMBER", 0, 1, 2)); - opcodes.insert(0x44, new_opcode("DIFFICULTY", 0, 1, 2)); - opcodes.insert(0x45, new_opcode("GASLIMIT", 0, 1, 2)); + // opcodes.insert(0x40, new_opcode("BLOCKHASH", 1, 1, 20)); + // opcodes.insert(0x41, new_opcode("COINBASE", 0, 1, 2)); + // opcodes.insert(0x42, new_opcode("TIMESTAMP", 0, 1, 2)); + // opcodes.insert(0x43, new_opcode("NUMBER", 0, 1, 2)); + // opcodes.insert(0x44, new_opcode("DIFFICULTY", 0, 1, 2)); + // opcodes.insert(0x45, new_opcode("GASLIMIT", 0, 1, 2)); // storage and execution - opcodes.insert(0x50, new_opcode("POP", 1, 0, 2)); + // opcodes.insert(0x50, new_opcode("POP", 1, 0, 2)); opcodes.insert(0x51, new_opcode("MLOAD", 1, 1, 3)); opcodes.insert(0x52, new_opcode("MSTORE", 2, 0, 3)); - opcodes.insert(0x53, new_opcode("MSTORE8", 2, 0, 3)); - opcodes.insert(0x54, new_opcode("SLOAD", 1, 1, 50)); + // opcodes.insert(0x53, new_opcode("MSTORE8", 2, 0, 3)); + // opcodes.insert(0x54, new_opcode("SLOAD", 1, 1, 50)); opcodes.insert(0x55, new_opcode("SSTORE", 2, 0, 0)); opcodes.insert(0x56, new_opcode("JUMP", 1, 0, 8)); opcodes.insert(0x57, new_opcode("JUMPI", 2, 0, 10)); - opcodes.insert(0x58, new_opcode("PC", 0, 1, 2)); - opcodes.insert(0x59, new_opcode("MSIZE", 0, 1, 2)); - opcodes.insert(0x5a, new_opcode("GAS", 0, 1, 2)); + // opcodes.insert(0x58, new_opcode("PC", 0, 1, 2)); + // opcodes.insert(0x59, new_opcode("MSIZE", 0, 1, 2)); + // opcodes.insert(0x5a, new_opcode("GAS", 0, 1, 2)); opcodes.insert(0x5b, new_opcode("JUMPDEST", 0, 0, 1)); // logging - opcodes.insert(0xa0, new_opcode("LOG0", 2, 0, 375)); - opcodes.insert(0xa1, new_opcode("LOG1", 3, 0, 750)); - opcodes.insert(0xa2, new_opcode("LOG2", 4, 0, 1125)); - opcodes.insert(0xa3, new_opcode("LOG3", 5, 0, 1500)); - opcodes.insert(0xa4, new_opcode("LOG4", 6, 0, 1875)); + // opcodes.insert(0xa0, new_opcode("LOG0", 2, 0, 375)); + // opcodes.insert(0xa1, new_opcode("LOG1", 3, 0, 750)); + // opcodes.insert(0xa2, new_opcode("LOG2", 4, 0, 1125)); + // opcodes.insert(0xa3, new_opcode("LOG3", 5, 0, 1500)); + // opcodes.insert(0xa4, new_opcode("LOG4", 6, 0, 1875)); // closures - opcodes.insert(0xf0, new_opcode("CREATE", 3, 1, 32000)); - opcodes.insert(0xf1, new_opcode("CALL", 7, 1, 40)); - opcodes.insert(0xf2, new_opcode("CALLCODE", 7, 1, 40)); + // opcodes.insert(0xf0, new_opcode("CREATE", 3, 1, 32000)); + // opcodes.insert(0xf1, new_opcode("CALL", 7, 1, 40)); + // opcodes.insert(0xf2, new_opcode("CALLCODE", 7, 1, 40)); opcodes.insert(0xf3, new_opcode("RETURN", 2, 0, 0)); - opcodes.insert(0xf4, new_opcode("DELEGATECALL", 6, 0, 40)); - opcodes.insert(0xff, new_opcode("SUICIDE", 1, 0, 0)); + // opcodes.insert(0xf4, new_opcode("DELEGATECALL", 6, 0, 40)); + // opcodes.insert(0xff, new_opcode("SUICIDE", 1, 0, 0)); for i in 1..33 { let name = format!("PUSH{}", i); @@ -169,7 +170,7 @@ impl Stack { if b0 >= b1 { self.push_arbitrary(&(b0 - b1).to_bytes_be()); } else { - // 2**256 + // 2**256 TODO this will not be here hardcoded, there will be a custom type uint256 let max = "115792089237316195423570985008687907853269984665640564039457584007913129639936" .parse::() @@ -206,10 +207,32 @@ impl Stack { self.push_arbitrary(&(b0 * b1 % b2).to_bytes_be()); } pub fn exp(&mut self) { - panic!("unimplemented"); - // let b0 = BigUint::from_bytes_be(&self.pop()[..]); - // let b1 = BigUint::from_bytes_be(&self.pop()[..]); - // self.push_arbitrary(&(pow(b0, b1)).to_bytes_be()); + let b = BigUint::from_bytes_be(&self.pop()[..]); + let e = BigUint::from_bytes_be(&self.pop()[..]); + + let mut r = "1".parse::().unwrap(); + let zero = "0".parse::().unwrap(); + let mut rem = e.clone(); + let mut exp = b; + // 2**256 TODO this will not be here hardcoded, there will be a custom type uint256 + let field = + "115792089237316195423570985008687907853269984665640564039457584007913129639936" + .parse::() + .unwrap(); + while rem != zero { + if rem.bit(0) { + // is odd + r = r * exp.clone() % field.clone(); + } + exp = exp.clone() * exp.clone(); + rem >>= 1; + } + self.push_arbitrary(&r.to_bytes_be()); + + let n_bytes = &e.to_bytes_be().len(); + let mut exp_fee = n_bytes * GEXPONENTBYTE; + exp_fee += EXP_SUPPLEMENTAL_GAS * n_bytes; + self.gas -= exp_fee as u64; } // boolean @@ -217,8 +240,33 @@ impl Stack { // contract context pub fn calldata_load(&mut self, calldata: &[u8]) { - self.put_arbitrary(&calldata[self.calldata_i..self.calldata_i + 32]); - self.calldata_i += 32; + self.put_arbitrary(&calldata[self.calldata_i..self.calldata_i + self.calldata_size]); + self.calldata_i += self.calldata_size; + } + pub fn calldata_size(&mut self, calldata: &[u8]) { + self.calldata_size = calldata.len(); + self.push(u256::usize_to_u256(self.calldata_size)); + } + fn spend_gas_data_copy(&mut self, length: usize) { + let length32 = upper_multiple_of_32(length); + self.gas -= ((GCOPY * length32) / 32) as u64; + } + pub fn code_copy(&mut self, code: &[u8]) { + let dest_offset = u256::u256_to_u64(self.pop()) as usize; + let offset = u256::u256_to_u64(self.pop()) as usize; + let length = u256::u256_to_u64(self.pop()) as usize; + + self.extend_mem(dest_offset, length); + self.spend_gas_data_copy(length); + + for i in 0..length { + if offset + i < code.len() { + self.mem[dest_offset + i] = code[offset + i]; + } else { + self.mem[dest_offset + i] = 0; + } + } + // self.mem[dest_offset..dest_offset+length] = } // blockchain context @@ -229,35 +277,59 @@ impl Stack { return; } let old_size = self.mem.len() / 32; - let new_size = (start + size) / 32; + let new_size = upper_multiple_of_32(start + size) / 32; let old_total_fee = old_size * GMEMORY + old_size.pow(2) / GQUADRATICMEMDENOM; let new_total_fee = new_size * GMEMORY + new_size.pow(2) / GQUADRATICMEMDENOM; let mem_fee = new_total_fee - old_total_fee; self.gas -= mem_fee as u64; - let mut new_bytes: Vec = vec![0; size]; + let mut new_bytes: Vec = vec![0; (new_size - old_size) * 32]; self.mem.append(&mut new_bytes); } pub fn mload(&mut self) { - let pos = u256_to_u64(self.pop()) as usize; + let pos = u256::u256_to_u64(self.pop()) as usize; self.extend_mem(pos as usize, 32); let mem32 = self.mem[pos..pos + 32].to_vec(); self.push_arbitrary(&mem32); } pub fn mstore(&mut self) { - let pos = u256_to_u64(self.pop()); + let pos = u256::u256_to_u64(self.pop()); let val = self.pop(); self.extend_mem(pos as usize, 32); self.mem[pos as usize..].copy_from_slice(&val); } + pub fn sstore(&mut self) { + let key = self.pop(); + let value = self.pop(); + if self.storage.contains_key(&key) { + let old_value = self.storage.get(&key).unwrap(); + if &value.to_vec() == old_value { + // if the new value is the same as the old one, do not set + return; + } + // if value (from self.pop()) does not exist in the stack, is a STORAGEKILL TODO + println!("mingas {:?}", GSTORAGEMOD); + self.gas -= GSTORAGEMOD as u64; + } else { + // if value does not exist, substract gas for the addition + println!("mingas {:?}", GSTORAGEADD); + self.gas -= GSTORAGEADD as u64; + } + println!( + "insert {:?} - {:?}", + vec_u8_to_hex(key.to_vec()), + vec_u8_to_hex(value.to_vec()) + ); + self.storage.insert(key, value.to_vec()); + } pub fn jump(&mut self) { // TODO that jump destination is valid - self.pc = u256_to_u64(self.pop()) as usize; + self.pc = u256::u256_to_u64(self.pop()) as usize; } pub fn jump_i(&mut self) { - let new_pc = u256_to_u64(self.pop()) as usize; + let new_pc = u256::u256_to_u64(self.pop()) as usize; if !self.stack.is_empty() { - let cond = u256_to_u64(self.pop()) as usize; + let cond = u256::u256_to_u64(self.pop()) as usize; if cond != 0 { self.pc = new_pc; } @@ -269,3 +341,7 @@ impl Stack { // TODO } } + +fn upper_multiple_of_32(n: usize) -> usize { + ((n - 1) | 31) + 1 +} diff --git a/tests/execute.rs b/tests/execute.rs index 2a95a99..0912563 100644 --- a/tests/execute.rs +++ b/tests/execute.rs @@ -3,15 +3,21 @@ use evm::*; #[test] fn stack_simple_push_pop() { let mut s = Stack::new(); - s.push(str_to_u256("1")); - s.push(str_to_u256("2")); - s.push(str_to_u256("3")); - assert_eq!(s.pop(), str_to_u256("3")); - assert_eq!(s.pop(), str_to_u256("2")); - assert_eq!(s.pop(), str_to_u256("1")); + s.push(u256::str_to_u256("1")); + s.push(u256::str_to_u256("2")); + s.push(u256::str_to_u256("3")); + assert_eq!(s.pop(), u256::str_to_u256("3")); + assert_eq!(s.pop(), u256::str_to_u256("2")); + assert_eq!(s.pop(), u256::str_to_u256("1")); // assert_eq!(s.pop(), error); // TODO expect error as stack is empty } +#[test] +fn available_opcodes() { + let s = Stack::new(); + println!("available opcodes {}/130", s.opcodes.len()); +} + // arithmetic #[test] fn execute_opcodes_0() { @@ -20,7 +26,7 @@ fn execute_opcodes_0() { let mut s = Stack::new(); s.execute(&code, &calldata, false); - assert_eq!(s.pop(), str_to_u256("17")); + assert_eq!(s.pop(), u256::str_to_u256("17")); assert_eq!(s.gas, 9999999991); assert_eq!(s.pc, 5); } @@ -50,7 +56,7 @@ fn execute_opcodes_2() { // assert_eq!(out[0], 0x09); assert_eq!(s.gas, 9999999991); assert_eq!(s.pc, 7); - assert_eq!(s.pop(), str_to_u256("515")); + assert_eq!(s.pop(), u256::str_to_u256("515")); } #[test] @@ -64,7 +70,7 @@ fn execute_opcodes_3() { assert_eq!(s.gas, 9999999985); assert_eq!(s.pc, 7); - assert_eq!(s.pop(), str_to_u256("9")); + assert_eq!(s.pop(), u256::str_to_u256("9")); } // storage and execution @@ -115,16 +121,86 @@ fn execute_opcodes_5() { assert_eq!(s.gas, 9999999864); assert_eq!(s.pc, 12); } -// #[test] -// fn execute_opcodes_6() { -// // 0x36: calldata_size -// let code = hex::decode("366020036101000a600035045b6001900380600c57").unwrap(); -// let calldata = hex::decode("05").unwrap(); -// -// let mut s = Stack::new(); -// s.execute(&code, &calldata, false); -// -// assert_eq!(s.gas, 9999999788); -// assert_eq!(s.pc, 21); -// assert_eq!(s.stack.len(), 0); -// } +#[test] +fn execute_opcodes_6() { + // 0x36: calldata_size + let code = hex::decode("366020036101000a600035045b6001900380600c57").unwrap(); + let calldata = hex::decode("01").unwrap(); + + let mut s = Stack::new(); + s.execute(&code, &calldata, false); + + assert_eq!(s.gas, 9999999892); + assert_eq!(s.pc, 21); + assert_eq!(s.stack.len(), 1); + + let code = hex::decode("366020036101000a600035045b6001900380600c57").unwrap(); + let calldata = hex::decode("05").unwrap(); + + let mut s = Stack::new(); + s.execute(&code, &calldata, false); + + assert_eq!(s.gas, 9999999788); + assert_eq!(s.pc, 21); + assert_eq!(s.stack.len(), 1); + + let code = hex::decode("366020036101000a600035045b6001900380600c57").unwrap(); + let calldata = hex::decode("0101").unwrap(); + + let mut s = Stack::new(); + s.execute(&code, &calldata, false); + + assert_eq!(s.gas, 9999993236); + assert_eq!(s.pc, 21); + assert_eq!(s.stack.len(), 1); +} + +#[test] +fn execute_opcodes_7() { + // contract deployment (code_copy) + let code = hex::decode("600580600b6000396000f36005600401").unwrap(); + let calldata = hex::decode("").unwrap(); + + let mut s = Stack::new(); + let out = s.execute(&code, &calldata, true); + + assert_eq!(s.gas, 9999999976); + assert_eq!(s.pc, 10); + assert_eq!(s.stack.len(), 0); + assert_eq!(s.mem.len(), 32); + assert_eq!( + s.mem, + hex::decode("6005600401000000000000000000000000000000000000000000000000000000").unwrap() + ); + assert_eq!(out, hex::decode("6005600401").unwrap()); +} + +#[test] +fn execute_opcodes_8() { + let code = hex::decode("611000805151").unwrap(); + let calldata = hex::decode("").unwrap(); + + let mut s = Stack::new(); + s.execute(&code, &calldata, false); + + assert_eq!(s.gas, 9999999569); + assert_eq!(s.pc, 6); + assert_eq!(s.stack.len(), 2); + assert_eq!(s.mem.len(), 4128); +} + +#[test] +fn execute_opcodes_9() { + // sstore (0x55) + let code = hex::decode("60026000556001600055").unwrap(); + let calldata = hex::decode("").unwrap(); + + let mut s = Stack::new(); + s.execute(&code, &calldata, true); + + assert_eq!(s.gas, 9999974988); + // assert_eq!(s.gas, 9999977788); // TODO WIP geth reported gas + assert_eq!(s.pc, 10); + assert_eq!(s.stack.len(), 0); + assert_eq!(s.storage.len(), 1); +}