Skip to content

Commit

Permalink
Merge pull request #170 from OffchainLabs/stylus
Browse files Browse the repository at this point in the history
  • Loading branch information
gzeoneth committed May 6, 2024
2 parents a00d2fa + 3ce2251 commit 77ee9de
Show file tree
Hide file tree
Showing 31 changed files with 1,938 additions and 301 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"@offchainlabs/upgrade-executor": "1.1.0-beta.0",
"@openzeppelin/contracts": "4.5.0",
"@openzeppelin/contracts-upgradeable": "4.5.2",
"patch-package": "^6.4.7"
"patch-package": "^6.4.7",
"solady": "0.0.182"
},
"private": false,
"devDependencies": {
Expand Down
208 changes: 208 additions & 0 deletions src/chain/CacheManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright 2022-2024, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.0;
import "../precompiles/ArbOwnerPublic.sol";
import "../precompiles/ArbWasm.sol";
import "../precompiles/ArbWasmCache.sol";
import "solady/src/utils/MinHeapLib.sol";

contract CacheManager {
using MinHeapLib for MinHeapLib.Heap;

ArbOwnerPublic internal constant ARB_OWNER_PUBLIC = ArbOwnerPublic(address(0x6b));
ArbWasm internal constant ARB_WASM = ArbWasm(address(0x71));
ArbWasmCache internal constant ARB_WASM_CACHE = ArbWasmCache(address(0x72));
uint64 internal constant MAX_MAKE_SPACE = 5 * 1024 * 1024;

MinHeapLib.Heap internal bids;
Entry[] public entries;

uint64 public cacheSize;
uint64 public queueSize;
uint64 public decay;
bool public isPaused;

error NotChainOwner(address sender);
error AsmTooLarge(uint256 asm, uint256 queueSize, uint256 cacheSize);
error AlreadyCached(bytes32 codehash);
error BidTooSmall(uint192 bid, uint192 min);
error BidsArePaused();
error MakeSpaceTooLarge(uint64 size, uint64 limit);

event InsertBid(bytes32 indexed codehash, uint192 bid, uint64 size);
event DeleteBid(bytes32 indexed codehash, uint192 bid, uint64 size);
event SetCacheSize(uint64 size);
event SetDecayRate(uint64 decay);
event Pause();
event Unpause();

struct Entry {
bytes32 code;
uint64 size;
}

constructor(uint64 initCacheSize, uint64 initDecay) {
cacheSize = initCacheSize;
decay = initDecay;
}

modifier onlyOwner() {
if (!ARB_OWNER_PUBLIC.isChainOwner(msg.sender)) {
revert NotChainOwner(msg.sender);
}
_;
}

/// Sets the intended cache size. Note that the queue may temporarily be larger.
function setCacheSize(uint64 newSize) external onlyOwner {
cacheSize = newSize;
emit SetCacheSize(newSize);
}

/// Sets the intended decay factor. Does not modify existing bids.
function setDecayRate(uint64 newDecay) external onlyOwner {
decay = newDecay;
emit SetDecayRate(newDecay);
}

/// Disable new bids.
function paused() external onlyOwner {
isPaused = true;
emit Pause();
}

/// Enable new bids.
function unpause() external onlyOwner {
isPaused = false;
emit Unpause();
}

/// Evicts all programs in the cache.
function evictAll() external onlyOwner {
evictPrograms(type(uint256).max);
delete entries;
}

/// Evicts up to `count` programs from the cache.
function evictPrograms(uint256 count) public onlyOwner {
while (bids.length() != 0 && count > 0) {
(uint192 bid, uint64 index) = _getBid(bids.pop());
_deleteEntry(bid, index);
count -= 1;
}
}

/// Sends all revenue to the network fee account.
function sweepFunds() external {
(bool success, bytes memory data) = ARB_OWNER_PUBLIC.getNetworkFeeAccount().call{
value: address(this).balance
}("");
if (!success) {
assembly {
revert(add(data, 32), mload(data))
}
}
}

/// Places a bid, reverting if payment is insufficient.
function placeBid(bytes32 codehash) external payable {
if (isPaused) {
revert BidsArePaused();
}
if (_isCached(codehash)) {
revert AlreadyCached(codehash);
}

uint64 asm = _asmSize(codehash);
(uint192 bid, uint64 index) = _makeSpace(asm);
return _addBid(bid, codehash, asm, index);
}

/// Evicts entries until enough space exists in the cache, reverting if payment is insufficient.
/// Returns the new amount of space available on success.
/// Note: will revert for requests larger than 5Mb. Call repeatedly for more.
function makeSpace(uint64 size) external payable returns (uint64 space) {
if (isPaused) {
revert BidsArePaused();
}
if (size > MAX_MAKE_SPACE) {
revert MakeSpaceTooLarge(size, MAX_MAKE_SPACE);
}
_makeSpace(size);
return cacheSize - queueSize;
}

/// Evicts entries until enough space exists in the cache, reverting if payment is insufficient.
/// Returns the bid and the index to use for insertion.
function _makeSpace(uint64 size) internal returns (uint192 bid, uint64 index) {
// discount historical bids by the number of seconds
bid = uint192(msg.value + block.timestamp * uint256(decay));
index = uint64(entries.length);

uint192 min;
uint64 limit = cacheSize;
while (queueSize + size > limit) {
(min, index) = _getBid(bids.pop());
_deleteEntry(min, index);
}
if (bid < min) {
revert BidTooSmall(bid, min);
}
}

/// Adds a bid
function _addBid(
uint192 bid,
bytes32 code,
uint64 size,
uint64 index
) internal {
if (queueSize + size > cacheSize) {
revert AsmTooLarge(size, queueSize, cacheSize);
}

Entry memory entry = Entry({size: size, code: code});
ARB_WASM_CACHE.cacheCodehash(code);
bids.push(_packBid(bid, index));
queueSize += size;
if (index == entries.length) {
entries.push(entry);
} else {
entries[index] = entry;
}
emit InsertBid(code, bid, size);
}

/// Clears the entry at the given index
function _deleteEntry(uint192 bid, uint64 index) internal {
Entry memory entry = entries[index];
ARB_WASM_CACHE.evictCodehash(entry.code);
queueSize -= entry.size;
emit DeleteBid(entry.code, bid, entry.size);
delete entries[index];
}

/// Gets the bid and index from a packed bid item
function _getBid(uint256 info) internal pure returns (uint192 bid, uint64 index) {
bid = uint192(info >> 64);
index = uint64(info);
}

/// Creates a packed bid item
function _packBid(uint192 bid, uint64 index) internal pure returns (uint256) {
return (uint256(bid) << 64) | uint256(index);
}

/// Gets the size of the given program in bytes
function _asmSize(bytes32 codehash) internal view returns (uint64) {
uint32 size = ARB_WASM.codehashAsmSize(codehash);
return uint64(size >= 4096 ? size : 4096); // pretend it's at least 4Kb
}

/// Determines whether a program is cached
function _isCached(bytes32 codehash) internal view returns (bool) {
return ARB_WASM_CACHE.codehashIsCached(codehash);
}
}
30 changes: 24 additions & 6 deletions src/challenge/ChallengeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager {
ISequencerInbox public sequencerInbox;
IBridge public bridge;
IOneStepProofEntry public osp;
mapping(bytes32 => IOneStepProofEntry) public ospCond;

function challengeInfo(uint64 challengeIndex)
external
Expand Down Expand Up @@ -110,12 +111,28 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager {
osp = osp_;
}

function postUpgradeInit(IOneStepProofEntry osp_) external onlyDelegated onlyProxyOwner {
// when updating to 4844 we need to create new osp contracts and set them here
// on the challenge manager
/// @dev A osp breaking change is introduced as part of Stylus upgrade, where the new osp would not support
/// pre-Stylus legacy wasmModuleRoot. To ensure that the new osp is not used for legacy wasmModuleRoot,
/// we introduce a conditional OSP where condRoot should be set to the pre-Stylus root and condOsp should
/// be set to the pre-Stylus osp. The correct value should be handled by the upgrade action contract.
function postUpgradeInit(
IOneStepProofEntry osp_,
bytes32 condRoot,
IOneStepProofEntry condOsp
) external onlyDelegated onlyProxyOwner {
ospCond[condRoot] = condOsp;
osp = osp_;
}

function getOsp(bytes32 wasmModuleRoot) public view returns (IOneStepProofEntry) {
IOneStepProofEntry t = ospCond[wasmModuleRoot];
if (address(t) == address(0)) {
return osp;
} else {
return t;
}
}

function createChallenge(
bytes32 wasmModuleRoot_,
MachineStatus[2] calldata startAndEndMachineStatuses_,
Expand Down Expand Up @@ -233,8 +250,9 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager {
}

bytes32[] memory segments = new bytes32[](2);
segments[0] = osp.getStartMachineHash(globalStateHashes[0], challenge.wasmModuleRoot);
segments[1] = osp.getEndMachineHash(machineStatuses[1], globalStateHashes[1]);
IOneStepProofEntry _osp = getOsp(challenge.wasmModuleRoot);
segments[0] = _osp.getStartMachineHash(globalStateHashes[0], challenge.wasmModuleRoot);
segments[1] = _osp.getEndMachineHash(machineStatuses[1], globalStateHashes[1]);

challenge.mode = ChallengeLib.ChallengeMode.EXECUTION;

Expand All @@ -256,7 +274,7 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager {
require(challengeLength == 1, "TOO_LONG");
}

bytes32 afterHash = osp.proveOneStep(
bytes32 afterHash = getOsp(challenge.wasmModuleRoot).proveOneStep(
ExecutionContext({maxInboxMessagesRead: challenge.maxInboxMessages, bridge: bridge}),
challengeStart,
selection.oldSegments[selection.challengePosition],
Expand Down
13 changes: 13 additions & 0 deletions src/challenge/IChallengeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ interface IChallengeManager {
IOneStepProofEntry osp_
) external;

function postUpgradeInit(
IOneStepProofEntry osp_,
bytes32 condRoot,
IOneStepProofEntry condOsp
) external;

/// @notice Get the default osp, which is used for all wasm module roots that don't have a conditional OSP set
/// Use getOsp(wasmModuleRoot) to get the OSP for a specific wasm module root
function osp() external view returns (IOneStepProofEntry);

/// @notice Get the OSP for a given wasm module root
function getOsp(bytes32 wasmModuleRoot) external view returns (IOneStepProofEntry);

function createChallenge(
bytes32 wasmModuleRoot_,
MachineStatus[2] calldata startAndEndMachineStatuses_,
Expand Down
52 changes: 52 additions & 0 deletions src/mocks/Benchmarks.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2022-2023, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.0;

contract Benchmarks {
function fillBlockRecover() external payable {
bytes32 bridgeToNova = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786;
address cryptoIsCute = 0x361594F5429D23ECE0A88E4fBE529E1c49D524d8;
uint8 v = 27;
bytes32 r = 0xc6178c2de1078cd36c3bd302cde755340d7f17fcb3fcc0b9c333ba03b217029f;
bytes32 s = 0x5fdbcefe2675e96219cdae57a7894280bf80fd40d44ce146a35e169ea6a78fd3;
while (true) {
require(ecrecover(bridgeToNova, v, r, s) == cryptoIsCute, "WRONG_ARBINAUT");
}
}

function fillBlockMulMod() external payable {
uint256 value = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786;
while (true) {
value = mulmod(
value,
0xc6178c2de1078cd36c3bd302cde755340d7f17fcb3fcc0b9c333ba03b217029f,
0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
);
}
}

function fillBlockHash() external payable {
bytes32 hash = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786;
while (true) {
hash = keccak256(abi.encodePacked(hash));
}
}

function fillBlockAdd() external payable {
uint256 value = 0;
while (true) {
unchecked {
value += 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786;
}
}
}

function fillBlockQuickStep() external payable {
uint256 value = 0;
while (true) {
value = msg.value;
}
}
}
Loading

0 comments on commit 77ee9de

Please sign in to comment.