kkrt-labs / kakarot-ssj Goto Github PK
View Code? Open in Web Editor NEWKakarot zkEVM - rewrite in the latest version of Cairo
Home Page: https://www.kakarot.org
License: MIT License
Kakarot zkEVM - rewrite in the latest version of Cairo
Home Page: https://www.kakarot.org
License: MIT License
Add a CHANGELOG.md file and make sure, in the CI, it has been updated for every PR on main
Index 1 is top of the stack. See PUSH.
a
: first binary value.b
: second binary value.a ^ b
: the bitwise XOR result.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
0xF0 |
0xFF |
* | 1 |
0xFF |
0 |
2 |
0xF |
* | 2 |
0xFF |
The state changes done by the current context are reverted in those cases:
Using overflowing_add here is equivalent to performing an addition modulo 2^256. In the ADDMOD case, we don't want this intermediate calculation to be modulo 2^256.
Instead, what we can do is either:
u512_safe_div_rem_by_u256
and keep the remainder(a mod N) + (b mod N) mod N
, since (a + b) mod n = [(a mod n) + (b mod n)] mod n.
The most gas-efficient solution of these two will be implemented.
Index 1 is top of the stack. See PUSH.
Shift the bits towards the least significant one. The bits moved before the first one are discarded, the new bits are set to 0 if the previous most significant bit was 0, otherwise the new bits are set to 1.
shift
: number of bits to shift to the right.value
: integer to shift.value >> shift
: the shifted value.* | Input | Output |
---|---|---|
1 |
1 |
1 |
2 |
2 |
* | Input | Output |
---|---|---|
1 |
4 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
2 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0 |
The state changes done by the current context are reverted in those cases:
Describe the Feature Request
Implement the compute intrinsic gas costs
as a method of ExecutionModel
.
This method will compute the gas cost of the current transaction, based on per transaction constant and cost of input data (16 gas per non-zero byte and 4 gas per zero byte), and return it.
Related Code
Additional Context
If the feature request is approved, would you be willing to submit a PR?
(Help can be provided if you need assistance submitting a PR)
Describe the Feature Request
On Cairo 0, we are currently storing all bytecodes as 1-byte data. So for a 10Kb contract, we are triggering 10.000 stores(when deploying the contract) and also 10.000 reads(on any contract call), which is expensive as for every storage or read a hash is calculated.
With storage improvement to 32 or 16 bytes, we decrease the amount of store and reads.
Describe Preferred/Possible Solution
Use storage index: Besides storing 32/16 bytes for each storage, once the hash for the storage location is calculated we can access up to 256 more slots by adding to the index.
Use dw(currently not possible). We could use dw to store the code in the contract and just access them as needed.
Related Code
Additional Context
If the feature request is approved, would you be willing to submit a PR?
(Help can be provided if you need assistance submitting a PR)
The goal of this issue is to discuss/determine what we need to run the general state tests to help guide potential contributions to starknet foundry.
I am assuming that it would be simpler to have a pre-running script that would take the ethereum test data and express it in terms of starknet types, so sn-foundry would just need to load the json.
Possible shape of running the GeneralStateTests:
The current implementation of the stack stores u256 by storing both the low and high part in two consecutive dict locations. We should refactor it to store Nullable<u256>
in the dict instead.
Describe the Feature Request
General State Tests are tests created by the ethereum foundation to test transaction handling.
The general structure is:
We should focus on using the structure of these tests, specifically for the Merge fork, to establish adequate transaction handling of our evm implementation.
Describe Preferred Solution
There are two distinct formats of GeneralStateTests that can be somewhat confusing:
General State Tests Format: This format is well-documented here and primarily focuses on the specifics of the state transition for Ethereum transactions.
Blockchain Tests Format: Within the blockchain tests folder structure, there's a sub-folder named /BlockchainTests/GeneralStateTests. This essentially contains a copy of the General State Tests. However, instead of just focusing on the state transition, it runs the tests within the broader context of blockchain logic. The same tests in a different format is explicitly mentioned here, but here in the latest docs.
For our purposes, we'll be utilizing both formats of GeneralStateTests. Why? The Blockchain Test format offers a more detailed post-state. By combining this with the GeneralStateTest format, we can concentrate on the state transition of transactions without the overhead of mimicking how ethereum manages state (via a merkle tree). This approach provides a streamlined method to assert post-state transitions.
In rust-like pseudocode, the general shape of test running would look like
let general_state_tests = directory("tests/GeneralStateTests/").filterJSONFiles();
for (general_state_test, general_state_test_filename) in general_state_tests {
// focus on the Merge fork
let sub_tests = general_state_test.post["Merge"];
for (sub_test, sub_test_idx) in sub_tests {
// fresh kakarot state is fresh starknet state, with the kakarot system deployed on top
let prepared_kakarot_state = fresh_kakarot_state();
// the transaction in a general state test has arrays for data, value, and gas for each transaction. the post state defines concrete indexes for the expected results
let concrete_transaction = general_state_test.transaction(sub_test.indexes.data, sub_test.indexes.value, sub_test.indexes.gas);
// eth->starknet types here
let kakarot_env = general_state_test.env.into();
let kakarot_transaction = concrete_transaction.into();
// initialize test's desired pre state
for (address, state) in general_state_test.pre {
prepared_kakarot_state.write(address, state);
}
// assuming we define a unique entry point for the general state tests, like we have for the `test_cases` for bytecode strings
let (return_data: felt*, updated_kakarot_state, events) = await kakarot_system.general_state_test_entry_point(prepared_kakarot_state, kakarot_env, kakarot_transaction);
// here load a unique file generated from a conformant evm (i.e. geth) to get the post state we check against
let post_state_check = subtest.post;
// we assert that the geth-evaluated trace matches our post state
assert_conformance!(updated_kakarot_state, post_state_check);
let eth_logs = event.into();
// need to convert starknet log data to ethereum logs and rlp encode and keccak hash the logs to assert equality
// see: https://github.com/ethereum/go-ethereum/blob/master/tests/state_test_util.go#L207
let hashed_rlp_logs = encode_and_hash_log(eth_logs);
assert_eq!(hashed_rlp_logs, sub_test.logs);
}
}
Related Code
Additional Context
If the feature request is approved, would you be willing to submit a PR?
(Help can be provided if you need assistance submitting a PR)
Memory is one of the core elements of the EVM. Having it as optimized as possible would reduce the overall costs of all transactions ran through the EVM.
Here is my proposal: We can refactor the memory to be 32-bytes words based instead of the current 16-bytes words based implementation.
I started to implement the new version to run some benchmarks. Below are the related code snippets.
fn store(ref self: Memory, element: u256, offset: usize) {
let x = testing::get_available_gas();
gas::withdraw_gas().unwrap();
let new_min_bytes_len = helpers::ceil_bytes_len_to_next_32_bytes_word(offset + 32);
let new_bytes_len = if new_min_bytes_len > self.bytes_len {
new_min_bytes_len
} else {
self.bytes_len
};
self.bytes_len = new_bytes_len;
// Check alignment of offset to bytes16 chunks
let (chunk_index, offset_in_chunk) = u32_safe_divmod(offset, u32_as_non_zero(16));
if offset_in_chunk == 0 {
// Offset is aligned. This is the simplest and most efficient case,
// so we optimize for it.
self.items.store_u256(element, chunk_index);
return ();
}
// Offset is misaligned.
// | W0 | W1 | w2 |
// | EL_H | EL_L |
// ^---^
// |-- mask = 256 ** offset_in_chunk
let mask: u256 = helpers::pow256_rev(offset_in_chunk);
let mask_c: u256 = utils::pow(256, 16).into() / mask;
// Split the 2 input bytes16 chunks at offset_in_chunk.
let (el_hh, el_hl) = u256_safe_div_rem(
u256 { low: element.high, high: 0 }, u256_as_non_zero(mask_c)
);
let (el_lh, el_ll) = u256_safe_div_rem(
u256 { low: element.low, high: 0 }, u256_as_non_zero(mask_c)
);
// Read the words at chunk_index, chunk_index + 2.
let w0: u128 = self.items.get(chunk_index.into());
let w2: u128 = self.items.get(chunk_index.into() + 2);
// Compute the new words
let w0_h: u256 = (w0.into() / mask);
let w2_l: u256 = (w2.into() / mask);
// We can convert them back to felt252 as we know they fit in one word.
let new_w0: u128 = (w0_h.into() * mask + el_hh).try_into().unwrap();
let new_w1: u128 = (el_hl.into() * mask + el_lh).try_into().unwrap();
let new_w2: u128 = (el_ll.into() * mask + w2_l).try_into().unwrap();
// Write the new words
self.items.insert(chunk_index.into(), new_w0);
self.items.insert(chunk_index.into() + 1, new_w1);
self.items.insert(chunk_index.into() + 2, new_w2);
(x - testing::get_available_gas()).print();
}
fn store(ref self: Memory, element: u256, offset: usize) {
let x = testing::get_available_gas();
gas::withdraw_gas().unwrap();
let new_min_bytes_len = helpers::ceil_bytes_len_to_next_32_bytes_word(offset + 32);
let new_bytes_len = if new_min_bytes_len > self.bytes_len {
new_min_bytes_len
} else {
self.bytes_len
};
self.bytes_len = new_bytes_len;
// Check alignment of offset to 32-bytes chunks
let (chunk_index, offset_in_chunk) = u32_safe_divmod(offset, u32_as_non_zero(32));
if offset_in_chunk == 0 {
// Offset is aligned. This is the simplest and most efficient case,
// so we optimize for it.
self.items.insert(chunk_index.into(), NullableExtensionTrait::new(element));
return ();
}
// Offset is misaligned.
// | W0 | W1 |
// | EL |
// ^---^
// |-- mask = 256 ** offset_in_chunk
let mask: u256 = helpers::pow256_rev(offset_in_chunk);
let mask_c: u256 = utils::pow(256, 32).into() / mask;
// Split the input two chunks at offset_in_chunk.
let (el_h, el_l) = u256_safe_div_rem(element, u256_as_non_zero(mask_c));
// Read the words at chunk_index, chunk_index + 1.
let w0: u256 = self.items.get(chunk_index.into()).deref_or_0();
let w1: u256 = self.items.get(chunk_index.into() + 1).deref_or_0();
// Compute the new words
let w0: u256 = (w0.into() / mask);
let w1: u256 = (w1.into() / mask);
// We can convert them back to felt252 as we know they fit in one word.
let new_w0: u256 = (w0.into() * mask + el_h);
let new_w1: u256 = (el_l.into() * mask + w1);
// Write the new words
self.items.insert(chunk_index.into(), NullableExtensionTrait::new(new_w0));
self.items.insert(chunk_index.into() + 1, NullableExtensionTrait::new(new_w1));
(x - testing::get_available_gas()).print();
}
}
I ran tests on both implementations and computed the actual gas usage of both functions. Here is the output
❯ scarb test -f test_store_should_add_an_element_to_the_memory_with_offset
testing kakarot ...
running 2 tests
[DEBUG(memory_32_bytes_chunks)] (raw: 0x36cfe
[DEBUG(memory_16_bytes_chunks)] (raw: 0x38e78
This leads to a ~4% gas decrease for each store
operation
Replace the 16-bytes words implementation with the 32-bytes words one.
Index 1 is top of the stack. See PUSH.
a
: first integer value to multiply.b
: second integer value to multiply.a * b
: integer result of the multiplication modulo 2256.* | Input | Output |
---|---|---|
1 |
10 |
100 |
2 |
10 |
* | Input | Output |
---|---|---|
1 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE |
2 |
2 |
The state changes done by the current context are reverted in those cases:
Index 1 is top of the stack. See PUSH.
a
: integer.a == 0
: 1 if a
is 0, 0 otherwise.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
10 |
0 |
* | 1 |
0 |
1 |
The state changes done by the current context are reverted in those cases:
From @enitrat
imo we go from
/// ADDMOD operation.
/// Addition modulo operation
/// # Additional informations:
/// - Since: Frontier
/// - Group: Stop and Arithmetic Operations
/// - Gas: 8
/// - Stack consumed elements: 2
/// - Stack produced elements: 1
/// # Arguments
/// * self - the execution context
fn exec_addmod(ref self: ExecutionContext) {
to
/// MULMOD operation.
/// (a * b) % N: integer result of the multiplication followed by a modulo.
/// All intermediate calculations of this operation are not subject to the 2^256 modulo.
/// If the denominator is 0, the result will be 0.
fn exec_mulmod(ref self: ExecutionContext) {
Describe the Feature Request
Describe Preferred Solution
Related Code
Additional Context
If the feature request is approved, would you be willing to submit a PR?
(Help can be provided if you need assistance submitting a PR)
SINCE GROUP
Frontier Stop and Arithmetic Operations
All intermediate calculations of this operation are not subject to the 2^256 modulo.
(a * b) % c: integer result of the multiplication followed by a modulo. If the denominator is 0, the result will be 0.
The state changes done by the current context are reverted in those cases:
Index 1 is top of the stack. See PUSH.
a
: first binary value.b
: second binary value.a & b
: the bitwise AND result.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
0xF |
0xF |
* | 1 |
0xFF |
0 |
2 |
0xF |
* | 2 |
0 |
The state changes done by the current context are reverted in those cases:
Describe the Feature Request
For the purpose of Kakarot, we need a Memory library that follows the EVM specification: a byte-size memory, accessed by offset.
Describe Preferred Solution
The Memory has 3 entry points:
offset
. If offset > memory.size(), expand memory.offset
, if offset > memory.size(), expand memoryoffset
into a variable. If offset > memory.size() - 32, expand memoryoffset
into a variable. If offset > memory.size() - 32, expand memoryoffset
Describe Alternatives
Related Code
https://github.com/sayajin-labs/kakarot/blob/main/src/kakarot/memory.cairo
Additional Context
This feature needs EXTENSIVE testing.
If the feature request is approved, would you be willing to submit a PR?
(Help can be provided if you need assistance submitting a PR)
We need to investigate what's the best pattern to use:
if x {}
if y {}
or
if x{}
else if y {}
Implement a u256 exponentiation math function.
You will compare the costs of using a fast-exponentiation algorithm vs. using a simple exponentiation algorithm, and submit the most adapted one
Describe the Feature Request
Describe Preferred Solution
Related Code
Additional Context
If the feature request is approved, would you be willing to submit a PR?
(Help can be provided if you need assistance submitting a PR)
Does having EVMInterpreter as an empty struct better than having, run, decode_and_execute and other functions inside a mod? Or maybe if they are a trait for ExecutionContext itself? Curious if there is any performance difference
e.g.
#[derive(Drop, Copy)]
struct EVMInterpreter {}
fn foo(){
let x = EVMInterpreter{};
x.run()
}
vs
```rust
mod EVMInterpreter {}
fn foo(){
EVMInterpreter::run()
}
Index 1 is top of the stack. See PUSH.
All values are treated as two's complement signed 256-bit integers.
a
: left side signed integer.b
: right side signed integer.a > b
: 1 if the left side is bigger, 0 otherwise.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
0 |
1 |
* | 1 |
10 |
0 |
2 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
* | 2 |
10 |
The state changes done by the current context are reverted in those cases:
SINCE | GROUP |
---|---|
Frontier | Stop and Arithmetic Operations |
The state changes done by the current context are reverted in those cases:
static_gas = 10
dynamic_gas = 50 * exponent_byte_size
The dynamic gas is simply a function of how many bytes we need to represent the exponent in binary.
SINCE GROUP Frontier Stop and Arithmetic Operations Index 1 is top of the stack. See [PUSH](https://www.evm.codes/#60).Index 1 is top of the stack. See PUSH.
a
: integer base.exponent
: integer exponent.a ** exponent
: integer result of the exponential operation modulo 2256.Input | Output | Input | Output | |||
---|---|---|---|---|---|---|
1 | 10 | 100 | 1 | 2 | 4 | |
2 | 2 | 2 | 2 |
Index 1 is top of the stack. See PUSH.
a
: left side integer.b
: right side integer.a == b
: 1 if the left side is equal to the right side, 0 otherwise.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
10 |
1 |
* | 1 |
10 |
0 |
2 |
10 |
* | 2 |
5 |
The state changes done by the current context are reverted in those cases:
Describe the Feature Request
In v0 Kakarot, we could run tests using pytest, we could load Json files and run tests with cases/ parameters: link
In v1 Kakarot, we want to be able to run ethereum/tests directly against our codebase.
For more information, we need to know about: kkrt-labs/kakarot#660
The core library doesn't expose a u256 mulmod libfunc, nor does it expose a normal function that performs (a*b) mod n.
We need to implement one for the #4 opcode
Describe the Feature Request
Describe Preferred Solution
Related Code
Additional Context
If the feature request is approved, would you be willing to submit a PR?
(Help can be provided if you need assistance submitting a PR)
We can introduce a .tools-version
file so that asdf detects the correct version of scarb
to use in the repository directory.
c.f. https://asdf-vm.com/manage/configuration.html
Index 1 is top of the stack. See PUSH.
All values are treated as two's complement signed 256-bit integers.
a
: left side signed integer.b
: right side signed integer.a < b
: 1 if the left side is smaller, 0 otherwise.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
1 |
* | 1 |
10 |
0 |
2 |
0 |
* | 2 |
10 |
The state changes done by the current context are reverted in those cases:
SINCE GROUP
Frontier - Comparison & Bitwise Logic Operations
Reproduce in playground.
a
: left side integer.b
: right side integer.a < b
: 1 if the left side is smaller, 0 otherwise.The state changes done by the current context are reverted in those cases:
Index 1 is top of the stack. See PUSH.
a
: left side integer.b
: right side integer.a > b
: 1 if the left side is bigger, 0 otherwise.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
10 |
1 |
* | 1 |
10 |
0 |
2 |
9 |
* | 2 |
10 |
The state changes done by the current context are reverted in those cases:
The SDIV Opcode performs signed division on u256 values.
There is no such thing as signed division on unsigned integers implemented yet in the core library, and the signed integer types do not go up to 256 bits yet.
Describe the Feature Request
Write a math library for signed integer division, using 256-bits values using two's complement
Describe Preferred Solution
Related Code
Additional Context
If the feature request is approved, would you be willing to submit a PR?
(Help can be provided if you need assistance submitting a PR)
Index 1 is top of the stack. See PUSH.
a
: first integer value.b
: integer value to subtract to the first.a - b
: integer result of the subtraction modulo 2256.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
10 |
0 |
* | 1 |
0 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
2 |
10 |
* | 2 |
1 |
The state changes done by the current context are reverted in those cases:
I have looked a bit into ssj, there still needs to be a lot (almost all) of issues created and spec’d
Namely:
Index 1 is top of the stack. See PUSH.
Shift the bits towards the least significant one. The bits moved before the first one are discarded, the new bits are set to 0.
shift
: number of bits to shift to the right.value
: 32 bytes to shift.value >> shift
: the shifted value. If shift
is bigger than 255, returns 0.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
1 |
1 |
* | 1 |
4 |
0xF |
2 |
2 |
* | 2 |
0xFF |
The state changes done by the current context are reverted in those cases:
Index 1 is top of the stack. See PUSH.
a
: first integer value to add.b
: second integer value to add.a + b
: integer result of the addition modulo 2256.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
10 |
20 |
* | 1 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
0 |
2 |
10 |
* | 2 |
1 |
The state changes done by the current context are reverted in those cases:
Index 1 is top of the stack. See PUSH.
Shift the bits towards the most significant one. The bits moved after the 256th one are discarded, the new bits are set to 0.
shift
: number of bits to shift to the left.value
: 32 bytes to shift.value << shift
: the shifted value. If shift
is bigger than 255, returns 0.* | Input | Output |
---|---|---|
1 |
1 |
2 |
2 |
1 |
* | Input | Output |
---|---|---|
1 |
4 |
0xF000000000000000000000000000000000000000000000000000000000000000 |
2 |
0xFF00000000000000000000000000000000000000000000000000000000000000 |
The state changes done by the current context are reverted in those cases:
Describe the Feature Request
We need to describe the new ExecutionContext in kakarot-ssj
Kakarot Cairo 0:
struct ExecutionContext {
call_context: CallContext*,
program_counter: felt,
stopped: felt,
return_data: felt*,
return_data_len: felt,
stack: Stack*,
memory: Memory*,
gas_used: felt,
gas_limit: felt,
gas_price: felt,
starknet_contract_address: felt,
evm_contract_address: felt,
calling_context: ExecutionContext*,
sub_context: ExecutionContext*,
destroy_contracts_len: felt,
destroy_contracts: felt*,
events_len: felt,
events: Event*,
create_addresses_len: felt,
create_addresses: felt*,
revert_contract_state: RevertContractState*,
reverted: felt,
read_only: felt,
}
Describe Preferred Solution
We could split the ExecutionContext well in themes:
dynamic, static data
Related Code
Additional Context
If the feature request is approved, would you be willing to submit a PR?
(Help can be provided if you need assistance submitting a PR)
After further investigatoin, this issue should not be implemented until we can properly benchmark the benefits of doing such an optimization. Relying on Sierra gas and testing::get_available_gas
doest not seem to be a good way to benchmark for now.
exploratory issue ->
go through the tutorial and migrate from scarb to sn foundry
we're not using smart contracts yet so no big change
We should use starknet-foundry to test our code rather than the native cairo-test
runner. It will have future feature that we will need.
We need #[should_panic]
to be available before that.
Index 1 is top of the stack. See PUSH.
a
: binary value.~a
: the bitwise NOT result.* | Input | Output |
---|---|---|
1 |
0 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
The state changes done by the current context are reverted in those cases:
Index 1 is top of the stack. See PUSH.
i
: byte offset starting from the most significant byte.x
: 32-byte value.y
: the indicated byte at the least significant position. If the byte offset is out of range, the result is 0.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
31 |
0xFF |
* | 1 |
30 |
0xFF |
2 |
0xFF |
* | 2 |
0xFF00 |
The state changes done by the current context are reverted in those cases:
Index 1 is top of the stack. See PUSH.
a
: integer numeratorb
: integer denominator.a // b
: integer result of the integer division. If the denominator is 0, the result will be 0.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
10 |
1 |
* | 1 |
1 |
0 |
2 |
10 |
* | 2 |
2 |
The state changes done by the current context are reverted in those cases:
Index 1 is top of the stack. See PUSH.
a
: first binary value.b
: second binary value.a | b
: the bitwise OR result.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
0xF0 |
0xFF |
* | 1 |
0xFF |
0xFF |
2 |
0xF |
* | 2 |
0xFF |
The state changes done by the current context are reverted in those cases:
Kakarot version:
Current implementation of modular opcodes operations should specifically handle the case where the divider is 0 - like it's done for div
.
Current behavior:
fn exec_mod(ref self: ExecutionContext) {
// Stack input:
// 0 = a: number
// 1 = b: modulo
let popped = self.stack.pop_n(2);
// Compute the result of a mod b
let result = *popped[0] % *popped[1];
// Stack output:
// a%b: unsigned integer result of a mod b
self.stack.push(result);
}
Expected behavior:
fn exec_mod(ref self: ExecutionContext) {
// Stack input:
// 0 = a: number
// 1 = b: modulo
let popped = self.stack.pop_n(2);
let mut result = 0;
let b = *popped[1];
if b != 0 {
// Compute the result of a mod b
result = *popped[0] % *popped[1];
}
// Stack output:
// a % b: integer result of the integer modulo. If the denominator is 0, the result will be 0.
self.stack.push(result);
}
Steps to reproduce:
Related code:
Other information:
Exits the current context successfully.
When a call is executed on an address with no code and the EVM tries to read the code data, the default value is returned, 0, which corresponds to this instruction and halts the execution.
SINCE GROUP
Frontier Stop and Arithmetic Operations
Reproduce in playground.
The state changes done by the current context are reverted in those cases:
SINCE GROUP Frontier Stop and Arithmetic Operations Index 1 is top of the stack. See PUSH.
All values are treated as two’s complement signed 256-bit integers. Note the overflow semantic when −2255 is negated.
a: integer. b: integer. c: integer.
a + b % c: integer result of the addition integer modulo.
The state changes done by the current context are reverted in those cases:
Index 1 is top of the stack. See PUSH.
All values are treated as two's complement signed 256-bit integers. Note the overflow semantic when 2255 is negated.
a
: integer numerator.b
: integer denominator.a // b
: integer result of the signed integer division. If the denominator is 0, the result will be 0.* | Input | Output | * | * | Input | Output |
---|---|---|---|---|---|---|
1 |
10 |
1 |
* | 1 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE |
2 |
2 |
10 |
* | 2 |
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
The state changes done by the current context are reverted in those cases:
While loops are compiled to recursion, there might be some computational overhead in using loops against recursion.
Write some computations with both behaviors, and compare their gas usage.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.