Skip to content

Commit

Permalink
Add MessageHubV1, ClustersNFTV1
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized committed Oct 2, 2024
1 parent 18cab24 commit 264460f
Show file tree
Hide file tree
Showing 11 changed files with 2,177 additions and 83 deletions.
9 changes: 6 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
path = lib/devtools
url = https://github.com/0xfoobar/devtools
branch = patch-1
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/Vectorized/solady
[submodule "lib/soledge"]
path = lib/soledge
url = https://github.com/Vectorized/soledge
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
[submodule "lib/multicaller"]
path = lib/multicaller
url = https://github.com/vectorized/multicaller
1 change: 1 addition & 0 deletions lib/multicaller
Submodule multicaller added at 356350
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
solmate/=lib/solmate/src/
solady/=lib/solady/src/
multicaller/=lib/multicaller/src/
soledge/=lib/soledge/src/
clusters/=src/
solidity-bytes-utils/=lib/LayerZero-v2/oapp/node_modules/solidity-bytes-utils/
Expand Down
367 changes: 321 additions & 46 deletions src/ClustersNFTV1.sol

Large diffs are not rendered by default.

184 changes: 151 additions & 33 deletions src/MessageHubV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,76 +3,194 @@ pragma solidity ^0.8.23;

import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {EfficientHashLib} from "solady/utils/EfficientHashLib.sol";
import {LibClone} from "solady/utils/LibClone.sol";
import {Pod} from "solady/accounts/Pod.sol";
import {ReentrancyGuard} from "soledge/utils/ReentrancyGuard.sol";
import {Origin, OAppReceiverUpgradeable} from "layerzero-oapp/contracts/oapp-upgradeable/OAppReceiverUpgradeable.sol";

contract MessageHubV1 is UUPSUpgradeable, OAppReceiverUpgradeable, ReentrancyGuard {
uint256 internal constant _MSG_SENDER_SLOT = 0;
uint256 internal constant _ORIGINAL_MSG_SENDER_SLOT = 1;
contract MessageHubV1Pod is Pod {
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* OVERRIDES */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

function mothership() public view override returns (address result) {
bytes memory args = LibClone.argsOnClone(address(this), 0x00, 0x20);
assembly ("memory-safe") {
result := mload(add(args, 0x20))
}
}

function _checkMothership() internal view override {
bytes memory args = LibClone.argsOnClone(address(this), 0x00, 0x40);
assembly ("memory-safe") {
let requiredOriginalSender := mload(add(args, 0x40))
let requiredCaller := mload(add(args, 0x20))
if iszero(
and(
and(eq(mload(0x20), requiredOriginalSender), eq(caller(), requiredCaller)),
staticcall(gas(), requiredCaller, 0x00, 0x00, 0x00, 0x40)
)
) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}

contract MessageHubV1PodMothership is ReentrancyGuard {
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* TRANSIENT STORAGE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

uint256 internal constant _SENDER_TRANSIENT_SLOT = 0;

uint256 internal constant _ORIGINAL_SENDER_TRANSIENT_SLOT = 1;

/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* ERRORS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

error Unauthorized();

error InsufficientMsgValue();

function initialize(address endpoint_, address owner_) external initializer {
_initializeOAppCore(endpoint_, owner_);
error OriginalSenderIsZero();

/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* IMMUTABLES */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

address internal immutable _podImplementation = address(new MessageHubV1Pod());

/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* PUBLIC VIEW FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

function predictSubAccount(bytes32 originalSender, uint256 senderType) public view returns (address) {
return LibClone.predictDeterministicAddress(
_podImplementation,
_subAccountArgs(originalSender),
EfficientHashLib.hash(uint256(originalSender), senderType),
address(this)
);
}

function _lzReceive(
Origin calldata, /* origin */
bytes32, /* guid */
bytes calldata message,
address, /* executor */
bytes calldata /* extraData */
) internal override nonReentrant {
(bytes32 originalMsgSender, bool isETHAddress, uint256 minimumMsgValue, address target, bytes memory data) =
abi.decode(message, (bytes32, bool, uint256, address, bytes));
receive() external payable {
assembly ("memory-safe") {
mstore(0x00, tload(_SENDER_TRANSIENT_SLOT))
mstore(0x20, tload(_ORIGINAL_SENDER_TRANSIENT_SLOT))
return(0x00, 0x40)
}
}

/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL HELPERS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

function _createSubAccount(bytes32 originalSender, uint256 senderType) internal returns (address) {
address predicted = predictSubAccount(originalSender, senderType);
if (_extcodesize(predicted) != 0) return predicted;
return LibClone.cloneDeterministic(
_podImplementation,
_subAccountArgs(originalSender),
EfficientHashLib.hash(uint256(originalSender), senderType)
);
}

function _extcodesize(address a) internal view returns (uint256 result) {
assembly ("memory-safe") {
result := extcodesize(a)
}
}

function _subAccountArgs(bytes32 originalSender) internal view returns (bytes memory result) {
if (originalSender == bytes32(0)) revert OriginalSenderIsZero();
assembly ("memory-safe") {
result := mload(0x40)
mstore(add(result, 0x40), originalSender)
mstore(add(result, 0x20), address())
mstore(result, 0x40)
mstore(0x40, add(result, 0x60))
}
}

function _forward(bytes calldata message) internal nonReentrant {
(bytes32 originalSender, uint256 senderType, uint256 minimumMsgValue, address target, bytes memory data) =
abi.decode(message, (bytes32, uint256, uint256, address, bytes));

if (originalSender == bytes32(0)) revert OriginalSenderIsZero();
if (msg.value < minimumMsgValue) revert InsufficientMsgValue();

address msgSender =
isETHAddress ? address(uint160(uint256(originalMsgSender))) : _msgSenderSubAccount(originalMsgSender);
address sender = address(uint160(uint256(originalSender)));
if (senderType != 0) {
sender = _createSubAccount(originalSender, senderType);
}

uint256 toRefund;
assembly ("memory-safe") {
let balanceBefore := sub(selfbalance(), callvalue())
tstore(_MSG_SENDER_SLOT, msgSender)
tstore(_ORIGINAL_MSG_SENDER_SLOT, originalMsgSender)
tstore(_SENDER_TRANSIENT_SLOT, sender)
tstore(_ORIGINAL_SENDER_TRANSIENT_SLOT, originalSender)
if iszero(call(gas(), target, callvalue(), add(data, 0x20), mload(data), 0x00, 0x00)) {
returndatacopy(mload(0x40), 0x00, returndatasize())
revert(mload(0x40), returndatasize())
}
tstore(_ORIGINAL_MSG_SENDER_SLOT, 0)
tstore(_MSG_SENDER_SLOT, 0)
tstore(_ORIGINAL_SENDER_TRANSIENT_SLOT, 0)
tstore(_SENDER_TRANSIENT_SLOT, 0)
toRefund := mul(sub(selfbalance(), balanceBefore), gt(selfbalance(), balanceBefore))
}
if (toRefund != 0) {
SafeTransferLib.forceSafeTransferETH(msgSender, toRefund);
SafeTransferLib.forceSafeTransferETH(sender, toRefund);
}
}
}

function _msgSenderSubAccount(bytes32 msgSender) internal returns (address subAccount) {}
contract MessageHubV1 is MessageHubV1PodMothership, UUPSUpgradeable, OAppReceiverUpgradeable {
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INITIALIZER */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

receive() external payable {
assembly ("memory-safe") {
mstore(0x00, tload(_MSG_SENDER_SLOT))
mstore(0x20, tload(_ORIGINAL_MSG_SENDER_SLOT))
return(0x00, 0x40)
}
constructor() payable {
_disableInitializers();
}

function name() public pure returns (string memory) {
return "MessageHub";
function initialize(address endpoint_, address owner_) public initializer {
_initializeOAppCore(endpoint_, owner_);
}

function version() public pure returns (string memory) {
return "1";
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* META */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

function contractNameAndVersion() public pure returns (string memory, string memory) {
return ("MessageHub", "1.0.0");
}

function withdraw(address to, uint256 amount) external {
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* ADMIN FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

function withdrawNative(address to, uint256 amount) public {
if (msg.sender != owner()) revert Unauthorized();
SafeTransferLib.safeTransferETH(to, amount);
}

/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* OVERRIDES */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

function _lzReceive(
Origin calldata, /* origin */
bytes32, /* guid */
bytes calldata message,
address, /* executor */
bytes calldata /* extraData */
) internal override {
_forward(message);
}

function _authorizeUpgrade(address) internal virtual override {
if (msg.sender != owner()) revert Unauthorized();
}
Expand Down
52 changes: 52 additions & 0 deletions test/ClustersNFTV1.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "./utils/SoladyTest.sol";
import "clusters/ClustersNFTV1.sol";

contract ClustersNFTV1Test is SoladyTest {
using ClustersNFTV1DataLib for *;

ClustersNFTV1DataLib.ClustersData internal _data0;
ClustersNFTV1DataLib.ClustersData internal _data1;

function testSetAndGetClustersData(bytes32) public {
do {
uint40 id0 = uint40(_random());
uint40 id1 = uint40(_random());
uint256 ownedIndex0 = _random();
uint256 ownedIndex1 = _random();
uint208 additionalData0 = uint208(_random());
uint208 additionalData1 = uint208(_random());

_data0.initialize(id0, ownedIndex0);
_data1.initialize(id1, ownedIndex1);
assertEq(_data0.getId(), id0);
assertEq(_data1.getId(), id1);
assertEq(_data0.getOwnedIndex(), ownedIndex0);
assertEq(_data1.getOwnedIndex(), ownedIndex1);

_data0.setAdditionalData(additionalData0);
_data1.setAdditionalData(additionalData1);
assertEq(_data0.getAdditionalData(), additionalData0);
assertEq(_data1.getAdditionalData(), additionalData1);

ownedIndex0 = _random();
ownedIndex1 = _random();
additionalData0 = uint208(_random());
additionalData1 = uint208(_random());

_data0.setOwnedIndex(ownedIndex0);
_data1.setOwnedIndex(ownedIndex1);
assertEq(_data0.getId(), id0);
assertEq(_data1.getId(), id1);
assertEq(_data0.getOwnedIndex(), ownedIndex0);
assertEq(_data1.getOwnedIndex(), ownedIndex1);

_data0.setAdditionalData(additionalData0);
_data1.setAdditionalData(additionalData1);
assertEq(_data0.getAdditionalData(), additionalData0);
assertEq(_data1.getAdditionalData(), additionalData1);
} while (_randomChance(2));
}
}
61 changes: 61 additions & 0 deletions test/MessageHubV1.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "./utils/SoladyTest.sol";
import "clusters/MessageHubV1.sol";

contract MothershipMock is MessageHubV1PodMothership {
function createSubAccount(bytes32 originalSender, uint256 senderType) public returns (address) {
return _createSubAccount(originalSender, senderType);
}

function forward(bytes calldata message) public payable {
_forward(message);
}
}

contract MessagehubV1Test is SoladyTest {
MothershipMock mothership;
uint256 x;
uint256 valueDuringSetX;

function setUp() public {
mothership = new MothershipMock();
}

function testMothership() public {
bytes32 originalSender = bytes32(_random());
while (originalSender == bytes32(0)) originalSender = bytes32(_random());

uint256 senderType = _random();
while (senderType == 0) senderType = _random();

address expected = mothership.predictSubAccount(originalSender, senderType);
assertEq(mothership.createSubAccount(originalSender, senderType), expected);
assertEq(MessageHubV1Pod(payable(expected)).mothership(), address(mothership));

uint256 newX = _random();

vm.deal(address(this), 10 ether);

bytes memory message = abi.encode(
originalSender,
senderType,
1 ether,
expected,
abi.encodeWithSignature(
"execute(address,uint256,bytes)", address(this), 1 ether, abi.encodeWithSignature("setX(uint256)", newX)
)
);

mothership.forward{value: 1 ether}(message);

assertEq(x, newX);
assertEq(valueDuringSetX, 1 ether);
}

function setX(uint256 value) public payable {
valueDuringSetX = msg.value;
x = value;
}
}
Loading

0 comments on commit 264460f

Please sign in to comment.