Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) core/vm: implement EIP-5656, mcopy opcode #697

Merged
merged 6 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
)

var activators = map[int]func(*JumpTable){
5656: enable5656,
3855: enable3855,
3860: enable3860,
3529: enable3529,
Expand Down Expand Up @@ -202,3 +203,29 @@ func enable3860(jt *JumpTable) {
jt[CREATE].dynamicGas = gasCreateEip3860
jt[CREATE2].dynamicGas = gasCreate2Eip3860
}

// enable5656 enables EIP-5656 (MCOPY opcode)
// https://eips.ethereum.org/EIPS/eip-5656
func enable5656(jt *JumpTable) {
jt[MCOPY] = &operation{
execute: opMcopy,
constantGas: GasFastestStep,
dynamicGas: gasMcopy,
minStack: minStack(3, 0),
maxStack: maxStack(3, 0),
memorySize: memoryMcopy,
}
}

// opMcopy implements the MCOPY opcode (https://eips.ethereum.org/EIPS/eip-5656)
func opMcopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
dst = scope.Stack.pop()
src = scope.Stack.pop()
length = scope.Stack.pop()
)
// These values are checked for overflow during memory expansion calculation
// (the memorySize function on the opcode).
scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64())
return nil, nil
}
2 changes: 2 additions & 0 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {
// as argument:
// CALLDATACOPY (stack position 2)
// CODECOPY (stack position 2)
// MCOPY (stack position 2)
// EXTCODECOPY (stack position 3)
// RETURNDATACOPY (stack position 2)
func memoryCopierGas(stackpos int) gasFunc {
Expand Down Expand Up @@ -89,6 +90,7 @@ func memoryCopierGas(stackpos int) gasFunc {
var (
gasCallDataCopy = memoryCopierGas(2)
gasCodeCopy = memoryCopierGas(2)
gasMcopy = memoryCopierGas(2)
gasExtCodeCopy = memoryCopierGas(3)
gasReturnDataCopy = memoryCopierGas(2)
)
Expand Down
142 changes: 142 additions & 0 deletions core/vm/instructions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ import (
"io/ioutil"
"testing"

"strings"

"github.com/holiman/uint256"

"github.com/scroll-tech/go-ethereum/common/math"

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/params"
Expand Down Expand Up @@ -651,3 +655,141 @@ func TestCreate2Addreses(t *testing.T) {
}
}
}

func TestOpMCopy(t *testing.T) {
// Test cases from https://eips.ethereum.org/EIPS/eip-5656#test-cases
for i, tc := range []struct {
dst, src, len string
pre string
want string
wantGas uint64
}{
{ // MCOPY 0 32 32 - copy 32 bytes from offset 32 to offset 0.
dst: "0x0", src: "0x20", len: "0x20",
pre: "0000000000000000000000000000000000000000000000000000000000000000 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
want: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
wantGas: 6,
},

{ // MCOPY 0 0 32 - copy 32 bytes from offset 0 to offset 0.
dst: "0x0", src: "0x0", len: "0x20",
pre: "0101010101010101010101010101010101010101010101010101010101010101",
want: "0101010101010101010101010101010101010101010101010101010101010101",
wantGas: 6,
},
{ // MCOPY 0 1 8 - copy 8 bytes from offset 1 to offset 0 (overlapping).
dst: "0x0", src: "0x1", len: "0x8",
pre: "000102030405060708 000000000000000000000000000000000000000000000000",
want: "010203040506070808 000000000000000000000000000000000000000000000000",
wantGas: 6,
},
{ // MCOPY 1 0 8 - copy 8 bytes from offset 0 to offset 1 (overlapping).
dst: "0x1", src: "0x0", len: "0x8",
pre: "000102030405060708 000000000000000000000000000000000000000000000000",
want: "000001020304050607 000000000000000000000000000000000000000000000000",
wantGas: 6,
},
// Tests below are not in the EIP, but maybe should be added
{ // MCOPY 0xFFFFFFFFFFFF 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds index(overlapping).
dst: "0xFFFFFFFFFFFF", src: "0xFFFFFFFFFFFF", len: "0x0",
pre: "11",
want: "11",
wantGas: 3,
},
{ // MCOPY 0xFFFFFFFFFFFF 0 0 - copy zero bytes from start of mem to out-of-bounds.
dst: "0xFFFFFFFFFFFF", src: "0x0", len: "0x0",
pre: "11",
want: "11",
wantGas: 3,
},
{ // MCOPY 0 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds to start of mem
dst: "0x0", src: "0xFFFFFFFFFFFF", len: "0x0",
pre: "11",
want: "11",
wantGas: 3,
},
{ // MCOPY - copy 1 from space outside of uint64 space
dst: "0x0", src: "0x10000000000000000", len: "0x1",
pre: "0",
},
{ // MCOPY - copy 1 from 0 to space outside of uint64
dst: "0x10000000000000000", src: "0x0", len: "0x1",
pre: "0",
},
{ // MCOPY - copy nothing from 0 to space outside of uint64
dst: "0x10000000000000000", src: "0x0", len: "0x0",
pre: "",
want: "",
wantGas: 3,
},
{ // MCOPY - copy 1 from 0x20 to 0x10, with no prior allocated mem
dst: "0x10", src: "0x20", len: "0x1",
pre: "",
// 64 bytes
want: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
wantGas: 12,
},
{ // MCOPY - copy 1 from 0x19 to 0x10, with no prior allocated mem
dst: "0x10", src: "0x19", len: "0x1",
pre: "",
// 32 bytes
want: "0x0000000000000000000000000000000000000000000000000000000000000000",
wantGas: 9,
},
} {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
)
data := common.FromHex(strings.ReplaceAll(tc.pre, " ", ""))
// Set pre
mem := NewMemory()
mem.Resize(uint64(len(data)))
mem.Set(0, uint64(len(data)), data)
// Push stack args
len, _ := uint256.FromHex(tc.len)
src, _ := uint256.FromHex(tc.src)
dst, _ := uint256.FromHex(tc.dst)

stack.push(len)
stack.push(src)
stack.push(dst)
wantErr := (tc.wantGas == 0)
// Calc mem expansion
var memorySize uint64
if memSize, overflow := memoryMcopy(stack); overflow {
if wantErr {
continue
}
t.Errorf("overflow")
} else {
var overflow bool
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
t.Error(ErrGasUintOverflow)
}
}
// and the dynamic cost
var haveGas uint64
if dynamicCost, err := gasMcopy(env, nil, stack, mem, memorySize); err != nil {
t.Error(err)
} else {
haveGas = GasFastestStep + dynamicCost
}
// Expand mem
if memorySize > 0 {
mem.Resize(memorySize)
}
// Do the copy
opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
want := common.FromHex(strings.ReplaceAll(tc.want, " ", ""))
if have := mem.store; !bytes.Equal(want, have) {
t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have)
}
wantGas := tc.wantGas
if haveGas != wantGas {
t.Errorf("case %d: gas wrong, want %d have %d\n", i, wantGas, haveGas)
}
}
}
1 change: 1 addition & 0 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type JumpTable [256]*operation
func newCurieInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198
enable5656(&instructionSet) // EIP-5656 (MCOPY opcode)
return instructionSet
}

Expand Down
11 changes: 11 additions & 0 deletions core/vm/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,14 @@ func (m *Memory) Print() {
}
fmt.Println("####################")
}

// Copy copies data from the src position slice into the dst position.
// The source and destination may overlap.
// OBS: This operation assumes that any necessary memory expansion has already been performed,
// and this method may panic otherwise.
func (m *Memory) Copy(dst, src, len uint64) {
if len == 0 {
return
}
copy(m.store[dst:], m.store[src:src+len])
}
8 changes: 8 additions & 0 deletions core/vm/memory_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ func memoryMStore(stack *Stack) (uint64, bool) {
return calcMemSize64WithUint(stack.Back(0), 32)
}

func memoryMcopy(stack *Stack) (uint64, bool) {
mStart := stack.Back(0) // stack[0]: dest
if stack.Back(1).Gt(mStart) {
mStart = stack.Back(1) // stack[1]: source
}
return calcMemSize64(mStart, stack.Back(2)) // stack[2]: length
}

func memoryCreate(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(1), stack.Back(2))
}
Expand Down
69 changes: 69 additions & 0 deletions core/vm/memory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package vm

import (
"bytes"
"strings"
"testing"

"github.com/scroll-tech/go-ethereum/common"
)

func TestMemoryCopy(t *testing.T) {
// Test cases from https://eips.ethereum.org/EIPS/eip-5656#test-cases
for i, tc := range []struct {
dst, src, len uint64
pre string
want string
}{
{ // MCOPY 0 32 32 - copy 32 bytes from offset 32 to offset 0.
0, 32, 32,
"0000000000000000000000000000000000000000000000000000000000000000 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
},

{ // MCOPY 0 0 32 - copy 32 bytes from offset 0 to offset 0.
0, 0, 32,
"0101010101010101010101010101010101010101010101010101010101010101",
"0101010101010101010101010101010101010101010101010101010101010101",
},
{ // MCOPY 0 1 8 - copy 8 bytes from offset 1 to offset 0 (overlapping).
0, 1, 8,
"000102030405060708 000000000000000000000000000000000000000000000000",
"010203040506070808 000000000000000000000000000000000000000000000000",
},
{ // MCOPY 1 0 8 - copy 8 bytes from offset 0 to offset 1 (overlapping).
1, 0, 8,
"000102030405060708 000000000000000000000000000000000000000000000000",
"000001020304050607 000000000000000000000000000000000000000000000000",
},
// Tests below are not in the EIP, but maybe should be added
{ // MCOPY 0xFFFFFFFFFFFF 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds index(overlapping).
0xFFFFFFFFFFFF, 0xFFFFFFFFFFFF, 0,
"11",
"11",
},
{ // MCOPY 0xFFFFFFFFFFFF 0 0 - copy zero bytes from start of mem to out-of-bounds.
0xFFFFFFFFFFFF, 0, 0,
"11",
"11",
},
{ // MCOPY 0 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds to start of mem
0, 0xFFFFFFFFFFFF, 0,
"11",
"11",
},
} {
m := NewMemory()
// Clean spaces
data := common.FromHex(strings.ReplaceAll(tc.pre, " ", ""))
// Set pre
m.Resize(uint64(len(data)))
m.Set(0, uint64(len(data)), data)
// Do the copy
m.Copy(tc.dst, tc.src, tc.len)
want := common.FromHex(strings.ReplaceAll(tc.want, " ", ""))
if have := m.store; !bytes.Equal(want, have) {
t.Errorf("case %d: want: %#x\nhave: %#x\n", i, want, have)
}
}
}
3 changes: 3 additions & 0 deletions core/vm/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ const (
MSIZE OpCode = 0x59
GAS OpCode = 0x5a
JUMPDEST OpCode = 0x5b
MCOPY OpCode = 0x5e
PUSH0 OpCode = 0x5f
)

Expand Down Expand Up @@ -302,6 +303,7 @@ var opCodeToString = map[OpCode]string{
MSIZE: "MSIZE",
GAS: "GAS",
JUMPDEST: "JUMPDEST",
MCOPY: "MCOPY",
PUSH0: "PUSH0",

// 0x60 range - push.
Expand Down Expand Up @@ -466,6 +468,7 @@ var stringToOp = map[string]OpCode{
"MSIZE": MSIZE,
"GAS": GAS,
"JUMPDEST": JUMPDEST,
"MCOPY": MCOPY,
"PUSH0": PUSH0,
"PUSH1": PUSH1,
"PUSH2": PUSH2,
Expand Down
2 changes: 1 addition & 1 deletion params/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
const (
VersionMajor = 5 // Major version component of the current release
VersionMinor = 3 // Minor version component of the current release
VersionPatch = 3 // Patch version component of the current release
VersionPatch = 4 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string
)

Expand Down
Loading