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

wip store event simulation #3302

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
154 changes: 154 additions & 0 deletions packages/store/src/StoreSimulator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;

import { Create2 } from "./vendor/Create2.sol";
import { IStore } from "./IStore.sol";
import { EncodedLengths } from "./EncodedLengths.sol";
import { ResourceId } from "./ResourceId.sol";
import { FieldLayout } from "./FieldLayout.sol";
import { StoreCore } from "./StoreCore.sol";

contract StoreProxy {
address public store;
bytes[] private calls;

constructor(address _store) {
store = _store;
}

function _calls() public view returns (bytes[] memory) {
return calls;
}

function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
EncodedLengths encodedLengths,
bytes calldata dynamicData
) public {
calls.push(msg.data);
StoreCore.setRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData);
}

function spliceStaticData(ResourceId tableId, bytes32[] calldata keyTuple, uint48 start, bytes calldata data) public {
calls.push(msg.data);
StoreCore.spliceStaticData(tableId, keyTuple, start, data);
}

function spliceDynamicData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint8 dynamicFieldIndex,
uint40 startWithinField,
uint40 deleteCount,
bytes calldata data
) public {
calls.push(msg.data);
StoreCore.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, startWithinField, deleteCount, data);
}

function setField(ResourceId tableId, bytes32[] calldata keyTuple, uint8 fieldIndex, bytes calldata data) public {
calls.push(msg.data);
StoreCore.setField(tableId, keyTuple, fieldIndex, data);
}

function setField(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint8 fieldIndex,
bytes calldata data,
FieldLayout fieldLayout
) public {
calls.push(msg.data);
StoreCore.setField(tableId, keyTuple, fieldIndex, data, fieldLayout);
}

function setStaticField(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint8 fieldIndex,
bytes calldata data,
FieldLayout fieldLayout
) public {
calls.push(msg.data);
StoreCore.setStaticField(tableId, keyTuple, fieldIndex, data, fieldLayout);
}

function setDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint8 dynamicFieldIndex,
bytes calldata data
) public {
calls.push(msg.data);
StoreCore.setDynamicField(tableId, keyTuple, dynamicFieldIndex, data);
}

function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint8 dynamicFieldIndex,
bytes calldata dataToPush
) public {
calls.push(msg.data);
StoreCore.pushToDynamicField(tableId, keyTuple, dynamicFieldIndex, dataToPush);
}

function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint8 dynamicFieldIndex,
uint256 byteLengthToPop
) public {
calls.push(msg.data);
StoreCore.popFromDynamicField(tableId, keyTuple, dynamicFieldIndex, byteLengthToPop);
}

function deleteRecord(ResourceId tableId, bytes32[] calldata keyTuple) public {
calls.push(msg.data);
StoreCore.deleteRecord(tableId, keyTuple);
}

fallback() external payable {
calls.push(msg.data);
(bool success, bytes memory result) = store.call(msg.data);
if (!success) {
assembly {
let returndata_size := mload(result)
revert(add(result, 0x20), returndata_size)
}
}
assembly {
return(add(result, 0x20), mload(result))
}
}

receive() external payable {
(bool success, bytes memory result) = store.call{ value: msg.value }("");
if (!success) {
assembly {
let returndata_size := mload(result)
revert(add(result, 0x20), returndata_size)
}
}
}
}

contract StoreSimulator {
error CallResult(bool success, bytes data, bytes[] calls);

function getProxyAddress(address store) public view returns (address) {
return Create2.computeAddress(0, keccak256(_getBytecode(store)));
}

function call(address store, bytes calldata callData) external {
address proxy = Create2.deploy(0, 0, _getBytecode(store));
(bool success, bytes memory data) = proxy.call(callData);
revert CallResult(success, data, StoreProxy(payable(proxy))._calls());
}

function _getBytecode(address store) internal pure returns (bytes memory) {
return abi.encodePacked(type(StoreProxy).creationCode, abi.encode(store));
}
}
92 changes: 92 additions & 0 deletions packages/store/src/vendor/Create2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Create2.sol)

pragma solidity ^0.8.20;

import { Errors } from "./Errors.sol";

/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();

/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
}
}

/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}

/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer

// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}
33 changes: 33 additions & 0 deletions packages/store/src/vendor/Errors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);

/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();

/**
* @dev The deployment failed.
*/
error FailedDeployment();

/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
Loading
Loading