Skip to content
This repository has been archived by the owner on Jan 9, 2025. It is now read-only.

Commit

Permalink
Use local state during a transaction (#771)
Browse files Browse the repository at this point in the history
Time spent on this PR: 2.5

## Pull request type

Please check the type of change your PR introduces:

- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] Documentation content changes
- [ ] Other (please describe):

## What is the current behavior?

Every opcode makes real changes to contract storages.
This is harder to revert and more expensive in steps.

## What is the new behavior?

A local `State` is created in the beginning of a tx/ExecutionContext and
used
as a buffer for SSTORE/SLOAD/BALANCE.

Changes are committed or discarded at the end in `finalize`.

The split between `ExecutionContext` and `CallContext` has been reviewed
to put in the `CallContext` every constants
parts of the `ExecutionContext`, ie parameters/values set at init and
not changed afterwards.

Resolves: #770 #726 #690 #689 #688
  • Loading branch information
ClementWalter authored Oct 27, 2023
1 parent bdc82af commit 748f50a
Show file tree
Hide file tree
Showing 55 changed files with 3,375 additions and 2,423 deletions.
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ markers = [
"MLOAD: Opcode Value 0x51 - Load word from memory",
"MSTORE: Opcode Value 0x52 - Save word to memory",
"MSTORE8: Opcode Value 0x53 - Save byte to memory",
"SLOAD: Opcode Value 0x54 - Load word from storage",
"SSTORE: Opcode Value 0x55 - Save word to storage",
"JUMP: Opcode Value 0x56 - Alter the program counter",
"JUMPI: Opcode Value 0x57 - Conditionally alter the program counter",
"PC: Opcode Value 0x58 - Get the value of the program counter prior to the increment",
Expand Down
2 changes: 1 addition & 1 deletion scripts/utils/kakarot.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async def deploy(
if success == 0:
raise EvmTransactionError(response)

evm_address, starknet_address = response
starknet_address, evm_address = response
contract.address = Web3.to_checksum_address(f"0x{evm_address:040x}")
contract.starknet_address = starknet_address
logger.info(f"✅ {contract_name} deployed at address {contract.address}")
Expand Down
35 changes: 33 additions & 2 deletions solidity_contracts/src/PlainOpcodes/PlainOpcodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity >=0.8.0;

import "./RevertTestCases.sol";
import "./Counter.sol";

interface ICounter {
function count() external view returns (uint256);
Expand Down Expand Up @@ -31,6 +32,11 @@ contract PlainOpcodes {
event Log3(address indexed owner, address indexed spender, uint256 value);
event Log4(address indexed owner, address indexed spender, uint256 indexed value);

event SentSome(address to, uint256 amount, bool success);
event NonceIncreased(uint256 nonce);

mapping(address => uint256) public nonces;

/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
Expand All @@ -41,6 +47,10 @@ contract PlainOpcodes {
/*//////////////////////////////////////////////////////////////
FUNCTIONS FOR OPCODES
//////////////////////////////////////////////////////////////*/
function incrementMapping() public {
emit NonceIncreased(nonces[msg.sender]++);
}

function opcodeBlockHash(uint256 blockNumber) public view returns (bytes32 _blockhash) {
return (blockhash(blockNumber));
}
Expand Down Expand Up @@ -110,6 +120,24 @@ contract PlainOpcodes {
emit Create2Address(_address);
}

function createCounterAndCall() public returns (address counter_) {
bytes memory bytecode = type(Counter).creationCode;
assembly {
counter_ := create(0, add(bytecode, 32), mload(bytecode))
}
ICounter(counter_).count();
emit CreateAddress(counter_);
}

function createCounterAndInvoke() public returns (address counter_) {
bytes memory bytecode = type(Counter).creationCode;
assembly {
counter_ := create(0, add(bytecode, 32), mload(bytecode))
}
ICounter(counter_).inc();
emit CreateAddress(counter_);
}

function requireNotZero(uint256 value) external pure {
require(value != 0, "ZERO_VALUE");
}
Expand Down Expand Up @@ -151,10 +179,13 @@ contract PlainOpcodes {

function sendSome(address payable to, uint256 amount) public {
bool success = to.send(amount);
require(success, "failed to send");
emit SentSome(to, amount, success);
}

function kill(address payable to) public {
function kill(address payable to) public payable {
selfdestruct(to);
}

receive() external payable {}
fallback() external payable {}
}
39 changes: 32 additions & 7 deletions solidity_contracts/src/PlainOpcodes/RevertTestCases.sol
Original file line number Diff line number Diff line change
@@ -1,26 +1,51 @@
pragma solidity >=0.8.0;

import "./Counter.sol";

contract ContractRevertsOnMethodCall {
Counter public counter;
uint public value;
event PartyTime(bool shouldDance);

uint256 public value;

event PartyTime(bool shouldDance);

function triggerRevert() public {
counter = new Counter();
counter = new Counter();
value = 1;
emit PartyTime(true);

emit PartyTime(true);
revert("FAIL");
}
}

contract ContractRevertsOnConstruction {
uint public value;
uint256 public value;

constructor() {
value = 42;
revert("FAIL");
}
}

contract ContractWithSelfdestructMethod {
uint256 public count;

constructor() payable {}

function inc() public {
count++;
}

function kill() public {
selfdestruct(payable(msg.sender));
}
}

contract ContractRevertOnFallbackAndReceive {
fallback() external payable {
revert("reverted on fallback");
}

receive() external payable {
revert("reverted on receive");
}
}
31 changes: 30 additions & 1 deletion solidity_contracts/tests/PlainOpcodes.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "forge-std/Test.sol";
import "forge-std/console.sol";

import {PlainOpcodes} from "../src/PlainOpcodes/PlainOpcodes.sol";
import {ContractRevertsOnMethodCall} from "../src/PlainOpcodes/RevertTestCases.sol";
import {ContractRevertsOnMethodCall, ContractWithSelfdestructMethod} from "../src/PlainOpcodes/RevertTestCases.sol";
import {Counter} from "../src/PlainOpcodes/Counter.sol";

contract PlainOpcodesTest is Test {
Expand Down Expand Up @@ -63,6 +63,19 @@ contract PlainOpcodesTest is Test {
assert(keccak256(bytes(errorMessage)) == keccak256("FAIL"));
}

function testSelfDestructAndCreateAgain() public {
bytes memory bytecode = type(ContractWithSelfdestructMethod).creationCode;
uint256 salt = 1234;
address addr = plainOpcodes.create2(bytecode, salt);
ContractWithSelfdestructMethod contract_ = ContractWithSelfdestructMethod(addr);
contract_.inc();
contract_.kill();
plainOpcodes.create2(bytecode, salt);
contract_.inc();
uint256 count = contract_.count();
assertEq(count, 2);
}

function testCreate() public {
uint256 count = 4;
address[] memory addresses = plainOpcodes.create(type(Counter).creationCode, count);
Expand All @@ -73,7 +86,23 @@ contract PlainOpcodesTest is Test {
}

function testSelfDestruct() public {
(bool success,) = address(plainOpcodes).call{value: 0.1 ether}("");
assert(success == true);

uint256 contractBalanceBefore = address(plainOpcodes).balance;
assert(contractBalanceBefore == 0.1 ether);
uint256 callerBalanceBefore = address(this).balance;

plainOpcodes.kill(payable(address(this)));

uint256 contractBalanceAfter = address(plainOpcodes).balance;
assert(contractBalanceAfter == 0);

// Balance is transferred immediately
uint256 callerBalanceAfter = address(this).balance;
assert(callerBalanceAfter - callerBalanceBefore == 0.1 ether);

// Account is still callable until the end of the tx
uint256 value = plainOpcodes.loop(10);
assert(value == 10);
}
Expand Down
Loading

0 comments on commit 748f50a

Please sign in to comment.