11defmodule EEVM.Opcodes.StackMemoryTest do
22 use ExUnit.Case , async: true
33
4+ alias EEVM.Gas
5+
46 describe "Executor - Memory" do
57 test "MSTORE and MLOAD" do
68 # PUSH1 0xFF, PUSH1 0, MSTORE, PUSH1 0, MLOAD, STOP
@@ -16,4 +18,126 @@ defmodule EEVM.Opcodes.StackMemoryTest do
1618 assert EEVM . stack_values ( result ) == [ 32 ]
1719 end
1820 end
21+
22+ # ── MCOPY Tests (EIP-5656) ──────
23+ describe "MCOPY (EIP-5656)" do
24+ test "non-overlapping copy" do
25+ code =
26+ IO . iodata_to_binary ( [
27+ mstore8 ( 0 , 0xAA ) ,
28+ mstore8 ( 1 , 0xBB ) ,
29+ mstore8 ( 2 , 0xCC ) ,
30+ mstore8 ( 3 , 0xDD ) ,
31+ mcopy ( 32 , 0 , 4 ) ,
32+ mload ( 32 ) ,
33+ << 0x00 >>
34+ ] )
35+
36+ result = EEVM . execute ( code )
37+ [ word ] = EEVM . stack_values ( result )
38+
39+ assert bytes32 ( word ) |> Enum . take ( 4 ) == [ 0xAA , 0xBB , 0xCC , 0xDD ]
40+ end
41+
42+ test "overlapping forward copy uses memmove semantics" do
43+ code =
44+ IO . iodata_to_binary ( [
45+ mstore8 ( 0 , 0x11 ) ,
46+ mstore8 ( 1 , 0x22 ) ,
47+ mstore8 ( 2 , 0x33 ) ,
48+ mstore8 ( 3 , 0x44 ) ,
49+ mcopy ( 1 , 0 , 3 ) ,
50+ mload ( 0 ) ,
51+ << 0x00 >>
52+ ] )
53+
54+ result = EEVM . execute ( code )
55+ [ word ] = EEVM . stack_values ( result )
56+
57+ assert bytes32 ( word ) |> Enum . take ( 4 ) == [ 0x11 , 0x11 , 0x22 , 0x33 ]
58+ end
59+
60+ test "overlapping backward copy uses memmove semantics" do
61+ code =
62+ IO . iodata_to_binary ( [
63+ mstore8 ( 0 , 0x11 ) ,
64+ mstore8 ( 1 , 0x22 ) ,
65+ mstore8 ( 2 , 0x33 ) ,
66+ mstore8 ( 3 , 0x44 ) ,
67+ mcopy ( 0 , 1 , 3 ) ,
68+ mload ( 0 ) ,
69+ << 0x00 >>
70+ ] )
71+
72+ result = EEVM . execute ( code )
73+ [ word ] = EEVM . stack_values ( result )
74+
75+ assert bytes32 ( word ) |> Enum . take ( 4 ) == [ 0x22 , 0x33 , 0x44 , 0x44 ]
76+ end
77+
78+ test "zero-length copy is a no-op and keeps msize unchanged" do
79+ code =
80+ IO . iodata_to_binary ( [
81+ mstore8 ( 0 , 0xAA ) ,
82+ << 0x59 >> ,
83+ mcopy ( 0 , 0 , 0 ) ,
84+ << 0x59 , 0x00 >>
85+ ] )
86+
87+ result = EEVM . execute ( code )
88+ assert EEVM . stack_values ( result ) == [ 32 , 32 ]
89+ end
90+
91+ test "gas calculation includes static, copy words, and memory expansion" do
92+ code = IO . iodata_to_binary ( [ mcopy ( 64 , 0 , 33 ) , << 0x00 >> ] )
93+ result = EEVM . execute ( code , gas: 100_000 )
94+
95+ expected =
96+ 3 + 3 + 3 + Gas . static_cost ( 0x5E ) + Gas . copy_cost ( 33 ) +
97+ Gas . memory_expansion_cost ( 0 , 0 , 97 )
98+
99+ assert result . status == :stopped
100+ assert result . gas == 100_000 - expected
101+ end
102+
103+ test "memory expansion covers both src and dst ranges" do
104+ code = IO . iodata_to_binary ( [ mcopy ( 0 , 96 , 32 ) , << 0x59 , 0x00 >> ] )
105+ result = EEVM . execute ( code )
106+ assert EEVM . stack_values ( result ) == [ 128 ]
107+ end
108+
109+ test "large copy across word boundaries" do
110+ writes =
111+ for i <- 0 .. 39 do
112+ mstore8 ( i , i + 1 )
113+ end
114+
115+ code =
116+ IO . iodata_to_binary ( [
117+ writes ,
118+ mcopy ( 64 , 0 , 40 ) ,
119+ mload ( 96 ) ,
120+ mload ( 64 ) ,
121+ << 0x00 >>
122+ ] )
123+
124+ result = EEVM . execute ( code )
125+ [ word1 , word2 ] = EEVM . stack_values ( result )
126+
127+ assert bytes32 ( word1 ) == Enum . to_list ( 1 .. 32 )
128+ assert bytes32 ( word2 ) |> Enum . take ( 8 ) == Enum . to_list ( 33 .. 40 )
129+ assert bytes32 ( word2 ) |> Enum . drop ( 8 ) == List . duplicate ( 0 , 24 )
130+ end
131+ end
132+
133+ defp push1 ( value ) , do: << 0x60 , value >>
134+
135+ defp mstore8 ( offset , byte ) , do: IO . iodata_to_binary ( [ push1 ( byte ) , push1 ( offset ) , << 0x53 >> ] )
136+
137+ defp mcopy ( dst , src , length ) ,
138+ do: IO . iodata_to_binary ( [ push1 ( length ) , push1 ( src ) , push1 ( dst ) , << 0x5E >> ] )
139+
140+ defp mload ( offset ) , do: IO . iodata_to_binary ( [ push1 ( offset ) , << 0x51 >> ] )
141+
142+ defp bytes32 ( word ) , do: :binary . bin_to_list ( << word :: unsigned - big - integer - size ( 256 ) >> )
19143end
0 commit comments