mirror of https://github.com/hyperledger/besu
Add EIP-4200: EOF - Static relative jumps (#4760)
* Remove opSize field from Operation With the introduction of RJUMPV, operations don't have fixed size anymore. This field isn't used anyware in the code so it's safe to delete it Signed-off-by: Diego López León <dieguitoll@gmail.com> * Add comments Signed-off-by: Diego López León <dieguitoll@gmail.com> * Add some EIP-3670 and EIP-3540 tests Signed-off-by: Diego López León <dieguitoll@gmail.com> * Replace jumpdest bitmap with BitSet Signed-off-by: Diego López León <dieguitoll@gmail.com> * Implement EIP-4200: EOF - Static relative jumps Signed-off-by: Diego López León <dieguitoll@gmail.com> Signed-off-by: Diego López León <dieguitoll@gmail.com>pull/4843/head
parent
8b46caf019
commit
c3c934d706
@ -0,0 +1,97 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.vm.operations; |
||||
|
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.evm.frame.MessageFrame; |
||||
import org.hyperledger.besu.evm.gascalculator.GasCalculator; |
||||
import org.hyperledger.besu.evm.operation.Operation; |
||||
import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; |
||||
import org.hyperledger.besu.evm.operation.RelativeJumpOperation; |
||||
import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.assertj.core.api.Assertions; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.ValueSource; |
||||
import org.mockito.Mockito; |
||||
|
||||
public class RelativeJumpOperationTest { |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(ints = {1, 0, 9, -4, -5}) |
||||
void rjumpOperation(final int jumpLength) { |
||||
final GasCalculator gasCalculator = mock(GasCalculator.class); |
||||
final MessageFrame messageFrame = mock(MessageFrame.class, Mockito.RETURNS_DEEP_STUBS); |
||||
final String twosComplementJump = String.format("%08x", jumpLength).substring(4); |
||||
final int rjumpOperationIndex = 3; |
||||
final Bytes code = Bytes.fromHexString("00".repeat(3) + "5c" + twosComplementJump); |
||||
|
||||
when(messageFrame.getCode().getCodeBytes()).thenReturn(code); |
||||
when(messageFrame.getRemainingGas()).thenReturn(3L); |
||||
when(messageFrame.getPC()).thenReturn(rjumpOperationIndex); |
||||
|
||||
RelativeJumpOperation rjump = new RelativeJumpOperation(gasCalculator); |
||||
Operation.OperationResult rjumpResult = rjump.execute(messageFrame, null); |
||||
|
||||
Assertions.assertThat(rjumpResult.getPcIncrement()) |
||||
.isEqualTo(code.size() - rjumpOperationIndex + jumpLength); |
||||
} |
||||
|
||||
@Test |
||||
void rjumpiOperation() { |
||||
final GasCalculator gasCalculator = mock(GasCalculator.class); |
||||
final MessageFrame messageFrame = mock(MessageFrame.class, Mockito.RETURNS_DEEP_STUBS); |
||||
final int rjumpOperationIndex = 3; |
||||
final Bytes code = Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5d0004"); |
||||
|
||||
when(messageFrame.getCode().getCodeBytes()).thenReturn(code); |
||||
when(messageFrame.getPC()).thenReturn(rjumpOperationIndex); |
||||
when(messageFrame.getRemainingGas()).thenReturn(5L); |
||||
when(messageFrame.popStackItem()).thenReturn(Bytes.EMPTY); |
||||
|
||||
RelativeJumpIfOperation rjumpi = new RelativeJumpIfOperation(gasCalculator); |
||||
Operation.OperationResult rjumpResult = rjumpi.execute(messageFrame, null); |
||||
|
||||
Assertions.assertThat(rjumpResult.getPcIncrement()).isEqualTo(2 + 1); |
||||
} |
||||
|
||||
@Test |
||||
void rjumpvOperation() { |
||||
final GasCalculator gasCalculator = mock(GasCalculator.class); |
||||
final MessageFrame messageFrame = mock(MessageFrame.class, Mockito.RETURNS_DEEP_STUBS); |
||||
final int rjumpOperationIndex = 3; |
||||
final int jumpVectorSize = 1; |
||||
final int jumpLength = 4; |
||||
final Bytes code = |
||||
Bytes.fromHexString( |
||||
"00".repeat(rjumpOperationIndex) |
||||
+ String.format("5e%02x%04x", jumpVectorSize, jumpLength)); |
||||
|
||||
when(messageFrame.getCode().getCodeBytes()).thenReturn(code); |
||||
when(messageFrame.getPC()).thenReturn(rjumpOperationIndex); |
||||
when(messageFrame.getRemainingGas()).thenReturn(5L); |
||||
when(messageFrame.popStackItem()).thenReturn(Bytes.of(jumpVectorSize)); |
||||
|
||||
RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); |
||||
Operation.OperationResult rjumpResult = rjumpv.execute(messageFrame, null); |
||||
|
||||
Assertions.assertThat(rjumpResult.getPcIncrement()).isEqualTo(1 + 2 * jumpVectorSize + 1); |
||||
} |
||||
} |
@ -0,0 +1,41 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
package org.hyperledger.besu.evm.operation; |
||||
|
||||
import org.hyperledger.besu.evm.EVM; |
||||
import org.hyperledger.besu.evm.frame.MessageFrame; |
||||
import org.hyperledger.besu.evm.gascalculator.GasCalculator; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class RelativeJumpIfOperation extends RelativeJumpOperation { |
||||
|
||||
public static final int OPCODE = 0x5d; |
||||
|
||||
public RelativeJumpIfOperation(final GasCalculator gasCalculator) { |
||||
super(OPCODE, "RJUMPI", 0, 0, gasCalculator, 4L); |
||||
} |
||||
|
||||
@Override |
||||
protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { |
||||
final Bytes condition = frame.popStackItem(); |
||||
// If condition is zero (false), no jump is will be performed. Therefore, skip the rest.
|
||||
if (!condition.isZero()) { |
||||
return super.executeFixedCostOperation(frame, evm); |
||||
} |
||||
return new OperationResult(gasCost, null, 2 + 1); |
||||
} |
||||
} |
@ -0,0 +1,65 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
package org.hyperledger.besu.evm.operation; |
||||
|
||||
import org.hyperledger.besu.evm.EVM; |
||||
import org.hyperledger.besu.evm.frame.MessageFrame; |
||||
import org.hyperledger.besu.evm.gascalculator.GasCalculator; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class RelativeJumpOperation extends AbstractFixedCostOperation { |
||||
|
||||
public static final int OPCODE = 0x5c; |
||||
|
||||
public RelativeJumpOperation(final GasCalculator gasCalculator) { |
||||
this(OPCODE, "RJUMP", 0, 0, gasCalculator, gasCalculator.getBaseTierGasCost()); |
||||
} |
||||
|
||||
protected RelativeJumpOperation( |
||||
final int opcode, |
||||
final String name, |
||||
final int stackItemsConsumed, |
||||
final int stackItemsProduced, |
||||
final GasCalculator gasCalculator, |
||||
final long fixedCost) { |
||||
super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator, fixedCost); |
||||
} |
||||
|
||||
@Override |
||||
protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { |
||||
final Bytes code = frame.getCode().getCodeBytes(); |
||||
final int pcPostInstruction = frame.getPC() + 1; |
||||
return new OperationResult(gasCost, null, 2 + getRelativeOffset(code, pcPostInstruction) + 1); |
||||
} |
||||
|
||||
/** |
||||
* Extracts the relative offset from the 16 bits after the RJUMP[I] opcode. This value is encoded |
||||
* as a 16-bit signed (two’s-complement) big-endian value |
||||
* |
||||
* @param code the source |
||||
* @param relativeOffsetBegin offset within the code where the immediate offset begins |
||||
* @return code immediate offset |
||||
*/ |
||||
public static int getRelativeOffset(final Bytes code, final int relativeOffsetBegin) { |
||||
int relativeOffset = |
||||
(code.get(relativeOffsetBegin) << 8) | (code.get(relativeOffsetBegin + 1) & 0xff); |
||||
if ((relativeOffset & 0x8000) != 0) { |
||||
relativeOffset = -((~relativeOffset & 0xffff) + 1); |
||||
} |
||||
return relativeOffset; |
||||
} |
||||
} |
@ -0,0 +1,53 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
package org.hyperledger.besu.evm.operation; |
||||
|
||||
import static org.hyperledger.besu.evm.operation.RelativeJumpOperation.getRelativeOffset; |
||||
|
||||
import org.hyperledger.besu.evm.EVM; |
||||
import org.hyperledger.besu.evm.frame.MessageFrame; |
||||
import org.hyperledger.besu.evm.gascalculator.GasCalculator; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class RelativeJumpVectorOperation extends AbstractFixedCostOperation { |
||||
|
||||
public static final int OPCODE = 0x5e; |
||||
|
||||
public RelativeJumpVectorOperation(final GasCalculator gasCalculator) { |
||||
super(OPCODE, "RJUMPV", 0, 0, gasCalculator, 4L); |
||||
} |
||||
|
||||
@Override |
||||
protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { |
||||
final Bytes code = frame.getCode().getCodeBytes(); |
||||
final int offsetCase = frame.popStackItem().toInt(); |
||||
final int vectorSize = getVectorSize(code, frame.getPC() + 1); |
||||
return new OperationResult( |
||||
gasCost, |
||||
null, |
||||
1 |
||||
+ 2 * vectorSize |
||||
+ ((offsetCase >= vectorSize) |
||||
? 0 |
||||
: getRelativeOffset(code, frame.getPC() + 1 + offsetCase * 2)) |
||||
+ 1); |
||||
} |
||||
|
||||
public static int getVectorSize(final Bytes code, final int offsetCountByteIndex) { |
||||
return code.get(offsetCountByteIndex) & 0xff; |
||||
} |
||||
} |
@ -0,0 +1,187 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.evm.Code; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
class CodeFactoryTest { |
||||
|
||||
@Test |
||||
void invalidCodeIncompleteMagic() { |
||||
invalidCode("0xEF"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeInvalidMagic() { |
||||
invalidCode("0xEFFF0101000302000400600000AABBCCDD"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoVersion() { |
||||
invalidCode("0xEF00"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeInvalidVersion0x00() { |
||||
invalidCode("EF000001000302000400600000AABBCCDD"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeInvalidVersion0x02() { |
||||
invalidCode("EF000201000302000400600000AABBCCDD"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeInvalidVersion0xFF() { |
||||
invalidCode("EF00FF01000302000400600000AABBCCDD"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoHeader() { |
||||
invalidCode("0xEF0001"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoCodeSection() { |
||||
invalidCode("0xEF000100"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoCodeSectionSize() { |
||||
invalidCode("0xEF000101"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeCodeSectionSizeIncomplete() { |
||||
invalidCode("0xEF00010100"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoSectionTerminator0x03() { |
||||
invalidCode("0xEF0001010003"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoSectionTerminator0x03600000() { |
||||
invalidCode("0xEF0001010003600000"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoCodeSectionContents() { |
||||
invalidCode("0xEF000101000200"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeCodeSectionContentsIncomplete() { |
||||
invalidCode("0xEF00010100020060"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeTrailingBytesAfterCodeSection() { |
||||
invalidCode("0xEF000101000300600000DEADBEEF"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeMultipleCodeSections() { |
||||
invalidCode("0xEF000101000301000300600000600000"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeEmptyCodeSection() { |
||||
invalidCode("0xEF000101000000"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeEmptyCodeSectionWithNonEmptyDataSection() { |
||||
invalidCode("0xEF000101000002000200AABB"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeDataSectionPrecedingCodeSection() { |
||||
invalidCode("0xEF000102000401000300AABBCCDD600000"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeDataSectionWithoutCodeSection() { |
||||
invalidCode("0xEF000102000400AABBCCDD"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoDataSectionSize() { |
||||
invalidCode("0xEF000101000202"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeDataSectionSizeIncomplete() { |
||||
invalidCode("0xEF00010100020200"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoSectionTerminator0x03020004() { |
||||
invalidCode("0xEF0001010003020004"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoSectionTerminator0x03020004600000AABBCCDD() { |
||||
invalidCode("0xEF0001010003020004600000AABBCCDD"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeNoDataSectionContents() { |
||||
invalidCode("0xEF000101000302000400600000"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeDataSectionContentsIncomplete() { |
||||
invalidCode("0xEF000101000302000400600000AABBCC"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeTrailingBytesAfterDataSection() { |
||||
invalidCode("0xEF000101000302000400600000AABBCCDDEE"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeMultipleDataSections() { |
||||
invalidCode("0xEF000101000302000402000400600000AABBCCDDAABBCCDD"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeMultipleCodeAndDataSections() { |
||||
invalidCode("0xEF000101000101000102000102000100FEFEAABB"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeEmptyDataSection() { |
||||
invalidCode("0xEF000101000302000000600000"); |
||||
} |
||||
|
||||
@Test |
||||
void invalidCodeUnknownSectionId3() { |
||||
invalidCode("0xEF0001010002030004006000AABBCCDD"); |
||||
} |
||||
|
||||
private static void invalidCode(final String str) { |
||||
Code code = CodeFactory.createCode(Bytes.fromHexString(str), Hash.EMPTY, 1, true); |
||||
assertThat(code.isValid()).isFalse(); |
||||
} |
||||
} |
Loading…
Reference in new issue