From bb23ed8a9aef016548546eddab163b300720d628 Mon Sep 17 00:00:00 2001 From: Zodomo Date: Thu, 18 Jan 2024 15:22:50 -0600 Subject: [PATCH 1/5] Initial spoke design --- src/ClustersHub.sol | 1 - src/ClustersSpoke.sol | 128 +++++++++ src/NameManagerSpoke.sol | 452 ++++++++++++++++++++++++++++++ src/interfaces/IClustersSpoke.sol | 109 +++++++ 4 files changed, 689 insertions(+), 1 deletion(-) create mode 100644 src/ClustersSpoke.sol create mode 100644 src/NameManagerSpoke.sol create mode 100644 src/interfaces/IClustersSpoke.sol diff --git a/src/ClustersHub.sol b/src/ClustersHub.sol index 226c44c..f554587 100644 --- a/src/ClustersHub.sol +++ b/src/ClustersHub.sol @@ -180,7 +180,6 @@ contract ClustersHub is NameManagerHub { } function _hookCheck(uint256 clusterId) internal view override { - if (clusterId == 0) return; if (_verifiedAddresses[clusterId].length() == 0) revert Invalid(); } diff --git a/src/ClustersSpoke.sol b/src/ClustersSpoke.sol new file mode 100644 index 0000000..3f10730 --- /dev/null +++ b/src/ClustersSpoke.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +// Follow https://docs.soliditylang.org/en/latest/style-guide.html for style + +import {EnumerableSetLib} from "./EnumerableSetLib.sol"; + +import {NameManagerSpoke} from "./NameManagerSpoke.sol"; + +import {IClustersSpoke} from "./interfaces/IClustersSpoke.sol"; + +import {IEndpoint} from "./interfaces/IEndpoint.sol"; + +import {console2} from "forge-std/Test.sol"; + +contract ClustersSpoke is NameManagerSpoke { + using EnumerableSetLib for EnumerableSetLib.Bytes32Set; + + uint256 public nextClusterId = 1; + + /// @dev Enumerates all unverified addresses in a cluster + mapping(uint256 clusterId => EnumerableSetLib.Bytes32Set addrs) internal _unverifiedAddresses; + + /// @dev Enumerates all verified addresses in a cluster + mapping(uint256 clusterId => EnumerableSetLib.Bytes32Set addrs) internal _verifiedAddresses; + + constructor(address owner_, address pricing_, address endpoint_, uint256 marketOpenTimestamp_) + NameManagerSpoke(owner_, pricing_, endpoint_, marketOpenTimestamp_) + {} + + /// OWNER FUNCTIONS /// + + function setStorageSlot(uint256 slot, bytes32 data) public onlyOwner { + assembly { + sstore(slot, data) + } + } + + /// VIEW FUNCTIONS /// + + function getUnverifiedAddresses(uint256 clusterId) external view returns (bytes32[] memory) { + return _unverifiedAddresses[clusterId].values(); + } + + function getVerifiedAddresses(uint256 clusterId) external view returns (bytes32[] memory) { + return _verifiedAddresses[clusterId].values(); + } + + /// ENDPOINT FUNCTIONS /// + + function add(bytes32 msgSender, bytes32 addr) public payable onlyEndpoint returns (bytes memory) { + _add(addr, addressToClusterId[msgSender]); + return bytes(""); + } + + function verify(bytes32 msgSender, uint256 clusterId) public payable onlyEndpoint returns (bytes memory payload) { + uint256 currentClusterId = addressToClusterId[msgSender]; + if (currentClusterId != 0) { + // If msgSender is the last address in their cluster, take all of their names with them + if (_verifiedAddresses[currentClusterId].length() == 1) { + bytes32[] memory names = _clusterNames[currentClusterId].values(); + for (uint256 i; i < names.length; ++i) { + _transferName(names[i], currentClusterId, clusterId); + } + } + _remove(msgSender, currentClusterId); + } + _verify(msgSender, clusterId); + return bytes(""); + } + + function remove(bytes32 msgSender, bytes32 addr) public payable onlyEndpoint returns (bytes memory) { + _remove(addr, addressToClusterId[msgSender]); + return bytes(""); + } + + /// INTERNAL FUNCTIONS /// + + function _add(bytes32 addr, uint256 clusterId) internal { + _unverifiedAddresses[clusterId].add(addr); + emit Add(clusterId, addr); + } + + function _verify(bytes32 addr, uint256 clusterId) internal { + _unverifiedAddresses[clusterId].remove(addr); + _verifiedAddresses[clusterId].add(addr); + addressToClusterId[addr] = clusterId; + emit Verify(clusterId, addr); + } + + function _remove(bytes32 addr, uint256 clusterId) internal { + _unverifiedAddresses[clusterId].remove(addr); + if (addressToClusterId[addr] == clusterId) { + delete addressToClusterId[addr]; + _verifiedAddresses[clusterId].remove(addr); + bytes32 walletName = reverseLookup[addr]; + if (walletName != bytes32("")) { + delete forwardLookup[clusterId][walletName]; + delete reverseLookup[addr]; + } + } + emit Remove(clusterId, addr); + } + + function _hookCreate(bytes32 addr) internal override { + uint256 clusterId = nextClusterId++; + _verifiedAddresses[clusterId].add(addr); + addressToClusterId[addr] = clusterId; + } + + function _hookDelete(uint256 clusterId) internal override { + bytes32[] memory addresses = _verifiedAddresses[clusterId].values(); + for (uint256 i; i < addresses.length; ++i) { + _remove(addresses[i], clusterId); + } + emit Delete(clusterId); + } + + function _hookCheck(uint256 clusterId) internal view override { + if (_verifiedAddresses[clusterId].length() == 0) revert Invalid(); + } + + function _hookCheck(uint256 clusterId, bytes32 addr) internal view override { + if (!_unverifiedAddresses[clusterId].contains(addr) && clusterId != addressToClusterId[addr]) { + revert Unauthorized(); + } + } +} diff --git a/src/NameManagerSpoke.sol b/src/NameManagerSpoke.sol new file mode 100644 index 0000000..82d42dc --- /dev/null +++ b/src/NameManagerSpoke.sol @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {EnumerableSetLib} from "./EnumerableSetLib.sol"; + +import {Ownable} from "solady/auth/Ownable.sol"; + +import {IPricing} from "./interfaces/IPricing.sol"; + +import {IClustersSpoke} from "./interfaces/IClustersSpoke.sol"; + +import {IEndpoint} from "./interfaces/IEndpoint.sol"; + +import {console2} from "forge-std/Test.sol"; + +/// @notice The bidding, accepting, eth storing component of Clusters. Handles name assignment +/// to cluster ids and checks auth of cluster membership before acting on one of its names +abstract contract NameManagerSpoke is IClustersSpoke, Ownable { + using EnumerableSetLib for EnumerableSetLib.Bytes32Set; + + bool internal _inMulticall; + + address public immutable endpoint; + + uint256 internal immutable marketOpenTimestamp; + + uint256 internal constant BID_TIMELOCK = 30 days; + + IPricing internal pricing; + + /// @notice Which cluster an address belongs to + mapping(bytes32 addr => uint256 clusterId) public addressToClusterId; + + /// @notice Which cluster a name belongs to + mapping(bytes32 name => uint256 clusterId) public nameToClusterId; + + /// @notice Display name to be shown for a cluster, like ENS reverse records + mapping(uint256 clusterId => bytes32 name) public defaultClusterName; + + /// @notice Enumerate all names owned by a cluster + mapping(uint256 clusterId => EnumerableSetLib.Bytes32Set names) internal _clusterNames; + + /// @notice For example lookup[17]["hot"] -> 0x123... + mapping(uint256 clusterId => mapping(bytes32 walletName => bytes32 addr)) public forwardLookup; + + /// @notice For example lookup[0x123...] -> "hot", then combine with cluster name in a diff method + mapping(bytes32 addr => bytes32 walletName) public reverseLookup; + + /// @notice Data required for proper harberger tax calculation when pokeName() is called + mapping(bytes32 name => IClustersSpoke.PriceIntegral integral) public priceIntegral; + + /// @notice The amount of money backing each name registration + mapping(bytes32 name => uint256 amount) public nameBacking; + + /// @notice Bid info storage, all bidIds are incremental and are not sorted by name + mapping(bytes32 name => IClustersSpoke.Bid bidData) public bids; + + /// @notice Failed bid refunds are pooled so we don't have to revert when the highest bid is outbid + mapping(bytes32 bidder => uint256 refund) public bidRefunds; + + /** + * PROTOCOL INVARIANT TRACKING + * address(this).balance >= protocolAccrual + totalNameBacking + totalBidBacking + */ + + /// @notice Amount of eth that's transferred from nameBacking to the protocol + uint256 public protocolAccrual; + + /// @notice Amount of eth that's backing names + uint256 public totalNameBacking; + + /// @notice Amount of eth that's sitting in active bids and canceled but not-yet-withdrawn bids + uint256 public totalBidBacking; + + /// @dev Ensures balance invariant holds + function _checkInvariant() internal view { + if (address(this).balance < protocolAccrual + totalNameBacking + totalBidBacking) revert BadInvariant(); + } + + /// @dev Ensure name is valid (not empty or too long) + function _checkNameValid(string memory name) internal pure { + if (bytes(name).length == 0) revert EmptyName(); + if (bytes(name).length > 32) revert LongName(); + } + + /// @dev Ensure addr owns name + function _checkNameOwnership(bytes32 addr, string memory name) internal view { + if (addressToClusterId[addr] == 0) revert NoCluster(); + if (bytes(name).length == 0) return; // Short circuit for reset as cluster addresses never own name "" + if (addressToClusterId[addr] != nameToClusterId[_stringToBytes32(name)]) revert Unauthorized(); + } + + /// @dev Ensure addr has a cluster + function _fixZeroCluster(bytes32 addr) internal { + if (addressToClusterId[addr] == 0) _hookCreate(addr); + } + + /// @dev Hook used to access _add() from Clusters.sol to abstract away cluster creation + function _hookCreate(bytes32 msgSender) internal virtual; + + /// @dev Hook used to access clusterAddresses() from Clusters.sol to delete clusters if all names are removed + function _hookDelete(uint256 clusterId) internal virtual; + + /// @dev Hook used to access cluster's _verifiedAddresses length to confirm cluster is valid before name transfer + function _hookCheck(uint256 clusterId) internal virtual; + + /// @dev Hook used to check if an address is either unverified or verified + function _hookCheck(uint256 clusterId, bytes32 addr) internal virtual; + + /// @notice Used to restrict external functions to endpoint + /// @dev This version ignores if msg.sender == msgSender as users wont be allowed to use these functions on spokes + modifier onlyEndpoint() { + if (msg.sender != endpoint) revert Unauthorized(); + _; + } + + constructor(address owner_, address pricing_, address endpoint_, uint256 marketOpenTimestamp_) { + if (marketOpenTimestamp_ < block.timestamp) revert Invalid(); + _initializeOwner(owner_); + pricing = IPricing(pricing_); + endpoint = endpoint_; + marketOpenTimestamp = marketOpenTimestamp_; + } + + /// VIEW FUNCTIONS /// + + /// @notice Get all names owned by a cluster in bytes32 format + /// @return names Array of names in bytes32 format + function getClusterNamesBytes32(uint256 clusterId) external view returns (bytes32[] memory names) { + return _clusterNames[clusterId].values(); + } + + /// ECONOMIC FUNCTIONS /// + + /// @notice Buy unregistered name. Must pay at least minimum yearly payment. + function buyName(bytes32 msgSender, uint256 msgValue, string memory name) + public + payable + onlyEndpoint + returns (bytes memory) + { + _fixZeroCluster(msgSender); + bytes32 _name = _stringToBytes32(name); + uint256 clusterId = addressToClusterId[msgSender]; + // Process price accounting updates + nameBacking[_name] += msgValue; + totalNameBacking += msgValue; + priceIntegral[_name] = IClustersSpoke.PriceIntegral({ + lastUpdatedTimestamp: block.timestamp, + lastUpdatedPrice: pricing.minAnnualPrice() + }); + _assignName(_name, clusterId); + if (defaultClusterName[clusterId] == bytes32("")) { + defaultClusterName[clusterId] = _name; + emit DefaultClusterName(_name, clusterId); + } + emit BuyName(_name, clusterId, msgValue); + + _checkInvariant(); + return bytes(""); + } + + /// @notice Fund an existing and specific name, callable by anyone + function fundName(bytes32 msgSender, uint256 msgValue, string memory name) + public + payable + onlyEndpoint + returns (bytes memory) + { + bytes32 _name = _stringToBytes32(name); + nameBacking[_name] += msgValue; + totalNameBacking += msgValue; + emit FundName(_name, msgSender, msgValue); + + _checkInvariant(); + return bytes(""); + } + + /// @notice Move name from one cluster to another without payment + function transferName(bytes32 msgSender, string memory name, uint256 toClusterId) + public + payable + onlyEndpoint + returns (bytes memory) + { + uint256 fromClusterId = addressToClusterId[msgSender]; + _transferName(_stringToBytes32(name), fromClusterId, toClusterId); + // Purge all addresses from cluster if last name was transferred out + if (_clusterNames[fromClusterId].length() == 0) _hookDelete(fromClusterId); + return bytes(""); + } + + /// @dev Transfer cluster name or delete cluster name without checking auth + /// @dev Delete by transferring to cluster id 0 + function _transferName(bytes32 name, uint256 fromClusterId, uint256 toClusterId) internal { + // Assign name to new cluster, otherwise unassign + if (toClusterId != 0) { + _unassignName(name, fromClusterId); + _assignName(name, toClusterId); + } else { + _unassignName(name, fromClusterId); + // Convert remaining name backing to protocol accrual and soft refund any existing bid + uint256 backing = nameBacking[name]; + delete nameBacking[name]; + totalNameBacking -= backing; + protocolAccrual += backing; + uint256 bid = bids[name].ethAmount; + if (bid > 0) { + bidRefunds[bids[name].bidder] += bid; + delete bids[name]; + } + } + emit TransferName(name, fromClusterId, toClusterId); + } + + // TODO: Figure out how pokeName() will be handled on spokes. Latency between mainnet and spoke calculations + // needs to be addressed. Maybe just pass the values directly from mainnet instead of calculating them? + /// @notice Move accrued revenue from ethBacked to protocolRevenue, and transfer names upon expiry to highest + /// sufficient bidder. If no bids above yearly minimum, delete name registration. + function pokeName(string memory name) public payable returns (bytes memory payload) { + if (msg.sender != endpoint) { + payload = abi.encodeWithSignature("pokeName(string)", name); + if (_inMulticall) return payload; + else return IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + } else { + bytes32 _name = _stringToBytes32(name); + IClustersSpoke.PriceIntegral memory integral = priceIntegral[_name]; + (uint256 spent, uint256 newPrice) = + pricing.getIntegratedPrice(integral.lastUpdatedPrice, block.timestamp - integral.lastUpdatedTimestamp); + // If out of backing (expired), transfer to highest sufficient bidder or delete registration + uint256 backing = nameBacking[_name]; + if (spent >= backing) { + delete nameBacking[_name]; + totalNameBacking -= backing; + protocolAccrual += backing; + // If there is a valid bid, transfer to the bidder + bytes32 bidder; + uint256 bid = bids[_name].ethAmount; + if (bid > 0) { + bidder = bids[_name].bidder; + _fixZeroCluster(bidder); + nameBacking[_name] += bid; + totalNameBacking += bid; + totalBidBacking -= bid; + delete bids[_name]; + } + // If there isn't a highest bidder, name will expire and be deleted as bidder is bytes32(0) + _transferName(_name, nameToClusterId[_name], addressToClusterId[bidder]); + } else { + // Process price data update + nameBacking[_name] -= spent; + totalNameBacking -= spent; + protocolAccrual += spent; + priceIntegral[_name] = + IClustersSpoke.PriceIntegral({lastUpdatedTimestamp: block.timestamp, lastUpdatedPrice: newPrice}); + } + emit PokeName(_name); + return bytes(""); + } + } + + /// @notice Place bids on valid names. Subsequent calls increases existing bid. If name is expired update ownership. + /// All bids timelocked for 30 days, unless they are outbid in which they are returned. Increasing a bid + /// resets the timelock. + /// @dev Should work smoothly for fully expired names and names partway through their duration + /// @dev Needs to be onchain ETH bid escrowed in one place because otherwise prices shift + function bidName(bytes32 msgSender, uint256 msgValue, string memory name) + public + payable + onlyEndpoint + returns (bytes memory) + { + bytes32 _name = _stringToBytes32(name); + // Retrieve bidder values to process refund in case they're outbid + uint256 prevBid = bids[_name].ethAmount; + bytes32 prevBidder = bids[_name].bidder; + // If the caller is the highest bidder, increase their bid and reset the timestamp + if (prevBidder == msgSender) { + bids[_name].ethAmount += msgValue; + totalBidBacking += msgValue; + // TODO: Determine which way is best to handle bid update timestamps + // bids[_name].createdTimestamp = block.timestamp; + emit BidIncreased(_name, msgSender, prevBid + msgValue); + } + // Process new highest bid + else { + // Overwrite previous bid + bids[_name] = IClustersSpoke.Bid(msgValue, block.timestamp, msgSender); + totalBidBacking += msgValue; + emit BidPlaced(_name, msgSender, msgValue); + // Process bid refund if there is one. Store balance for recipient if transfer fails instead of reverting. + if (prevBid > 0) { + (bool success,) = payable(_bytes32ToAddress(prevBidder)).call{value: prevBid}(""); + if (!success) { + bidRefunds[prevBidder] += prevBid; + } else { + totalBidBacking -= prevBid; + emit BidRefunded(_name, prevBidder, msgValue); + } + } + } + // Update name status and transfer to highest bidder if expired + // TODO: Revisit this call when spoke pokeName() logic is reevaluated + pokeName(name); + + _checkInvariant(); + return bytes(""); + } + + /// @notice Reduce bid and refund difference. Revoke if amount is the total bid or is the max uint256 value. + function reduceBid(bytes32 msgSender, string memory name, uint256 amount) + public + payable + onlyEndpoint + returns (bytes memory) + { + bytes32 _name = _stringToBytes32(name); + uint256 bid = bids[_name].ethAmount; + // Overwrite amount with total bid in assumption caller is revoking bid + if (amount > bid) amount = bid; + + // Poke name to update backing and ownership (if required) prior to bid adjustment + // TODO: Revisit this call when spoke pokeName() logic is reevaluated + pokeName(name); + + // Skip bid reduction logic if pokeName() processed transfer to bidder due to name expiry + if (bids[_name].ethAmount != 0) { + uint256 diff = bid - amount; + // If reducing bid to 0 or by maximum uint256 value, revoke altogether + if (diff == 0) { + delete bids[_name]; + totalBidBacking -= bid; + emit BidRevoked(_name, msgSender, bid); + } + // Otherwise, decrease bid and update timestamp + else { + bids[_name].ethAmount -= amount; + totalBidBacking -= amount; + // TODO: Determine which way is best to handle bid update timestamps + // bids[_name].createdTimestamp = block.timestamp; + emit BidReduced(_name, msgSender, amount); + } + } + return bytes(""); + } + + /// @notice Accept bid and transfer name to bidder + /// @dev Retrieves bid, adjusts state, then sends payment to avoid reentrancy + function acceptBid(bytes32 msgSender, string memory name) + public + payable + onlyEndpoint + returns (bytes memory payload) + { + bytes32 _name = _stringToBytes32(name); + Bid memory bid = bids[_name]; + delete bids[_name]; + totalBidBacking -= bid.ethAmount; + _fixZeroCluster(bid.bidder); + _transferName(_name, addressToClusterId[msgSender], addressToClusterId[bid.bidder]); + return bytes(""); + } + + /// @notice Allow failed bid refunds to be withdrawn + function refundBid(bytes32 msgSender) public payable onlyEndpoint returns (bytes memory) { + uint256 refund = bidRefunds[msgSender]; + delete bidRefunds[msgSender]; + totalBidBacking -= refund; + return bytes(""); + } + + /// LOCAL NAME MANAGEMENT /// + + /// @notice Set canonical name + function setDefaultClusterName(bytes32 msgSender, string memory name) + public + payable + onlyEndpoint + returns (bytes memory) + { + bytes32 _name = _stringToBytes32(name); + uint256 clusterId = addressToClusterId[msgSender]; + defaultClusterName[clusterId] = _name; + emit DefaultClusterName(_name, clusterId); + return bytes(""); + } + + /// @notice Set wallet name for addr + function setWalletName(bytes32 msgSender, bytes32 addr, string memory walletName) + public + payable + onlyEndpoint + returns (bytes memory) + { + uint256 clusterId = addressToClusterId[msgSender]; + bytes32 _walletName = _stringToBytes32(walletName); + if (bytes(walletName).length == 0) { + _walletName = reverseLookup[addr]; + delete forwardLookup[clusterId][_walletName]; + delete reverseLookup[addr]; + emit SetWalletName(bytes32(""), addr); + } else { + bytes32 prev = reverseLookup[addr]; + if (prev != bytes32("")) delete forwardLookup[clusterId][prev]; + forwardLookup[clusterId][_walletName] = addr; + reverseLookup[addr] = _walletName; + emit SetWalletName(_walletName, addr); + } + return bytes(""); + } + + /// @dev Set name-related state variables + function _assignName(bytes32 name, uint256 clusterId) internal { + nameToClusterId[name] = clusterId; + _clusterNames[clusterId].add(name); + } + + /// @dev Purge name-related state variables + function _unassignName(bytes32 name, uint256 clusterId) internal { + delete nameToClusterId[name]; + _clusterNames[clusterId].remove(name); + // If name is default cluster name for clusterId, reassign to the name at index 0 in _clusterNames + if (defaultClusterName[clusterId] == name) { + if (_clusterNames[clusterId].length() == 0) { + delete defaultClusterName[clusterId]; + emit DefaultClusterName(bytes32(""), clusterId); + } else { + bytes32 newDefaultName = _clusterNames[clusterId].at(0); + defaultClusterName[clusterId] = newDefaultName; + emit DefaultClusterName(newDefaultName, clusterId); + } + } + } + + /// STRING HELPERS /// + + /// @dev Returns bytes32 representation of string < 32 characters, used in name-related state vars and functions + function _stringToBytes32(string memory smallString) internal pure returns (bytes32 result) { + bytes memory smallBytes = bytes(smallString); + return bytes32(smallBytes); + } + + /// @dev Returns bytes32 representation of address + function _addressToBytes32(address addr) internal pure returns (bytes32) { + return bytes32(uint256(uint160(addr))); + } + + /// @dev Returns address representation of bytes32 + function _bytes32ToAddress(bytes32 addr) internal pure returns (address) { + return address(uint160(uint256(addr))); + } +} diff --git a/src/interfaces/IClustersSpoke.sol b/src/interfaces/IClustersSpoke.sol new file mode 100644 index 0000000..43d6f93 --- /dev/null +++ b/src/interfaces/IClustersSpoke.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +interface IClustersSpoke { + /// STRUCTS /// + + struct PriceIntegral { + uint256 lastUpdatedTimestamp; + uint256 lastUpdatedPrice; + } + + /// @notice All relevant information for an individual bid + struct Bid { + uint256 ethAmount; + uint256 createdTimestamp; + bytes32 bidder; + } + + /// EVENTS /// + + event Add(uint256 indexed clusterId, bytes32 indexed addr); + event Remove(uint256 indexed clusterId, bytes32 indexed addr); + event Verify(uint256 indexed clusterId, bytes32 indexed addr); + event Delete(uint256 indexed clusterId); + + event BuyName(bytes32 indexed name, uint256 indexed clusterId, uint256 indexed amount); + event FundName(bytes32 indexed name, bytes32 indexed funder, uint256 indexed amount); + event TransferName(bytes32 indexed name, uint256 indexed fromClusterId, uint256 indexed toClusterId); + event PokeName(bytes32 indexed name); + event DefaultClusterName(bytes32 indexed name, uint256 indexed clusterId); + event SetWalletName(bytes32 indexed walletName, bytes32 indexed wallet); + + event BidPlaced(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); + event BidRefunded(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); + event BidIncreased(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); + event BidReduced(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); + event BidRevoked(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); + + /// ERRORS /// + + error NoBid(); + error SelfBid(); + error Invalid(); + error Timelock(); + error LongName(); + error Insolvent(); + error EmptyName(); + error NoCluster(); + error Registered(); + error Unregistered(); + error Insufficient(); + error BadInvariant(); + error MulticallFailed(); + error NativeTokenTransferFailed(); + + /// STORAGE / VIEW FUNCTIONS /// + + function endpoint() external view returns (address endpoint); + function nextClusterId() external view returns (uint256 clusterId); + function addressToClusterId(bytes32 addr) external view returns (uint256 clusterId); + function nameToClusterId(bytes32 name) external view returns (uint256 clusterId); + function defaultClusterName(uint256 clusterId) external view returns (bytes32 name); + function forwardLookup(uint256 clusterId, bytes32 walletName) external view returns (bytes32 addr); + function reverseLookup(bytes32 addr) external view returns (bytes32 walletName); + + function priceIntegral(bytes32 name) + external + view + returns (uint256 lastUpdatedTimestamp, uint256 lastUpdatedPrice); + function nameBacking(bytes32 name) external view returns (uint256 ethAmount); + function bids(bytes32 name) external view returns (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder); + function bidRefunds(bytes32 bidder) external view returns (uint256 refund); + + function protocolAccrual() external view returns (uint256 accrual); + function totalNameBacking() external view returns (uint256 nameBacking); + function totalBidBacking() external view returns (uint256 bidBacking); + + function getUnverifiedAddresses(uint256 clusterId) external view returns (bytes32[] memory addresses); + function getVerifiedAddresses(uint256 clusterId) external view returns (bytes32[] memory addresses); + function getClusterNamesBytes32(uint256 clusterId) external view returns (bytes32[] memory names); + + /// EXTERNAL FUNCTIONS /// + + function add(bytes32 msgSender, bytes32 addr) external payable returns (bytes memory); + function verify(bytes32 msgSender, uint256 clusterId) external payable returns (bytes memory); + function remove(bytes32 msgSender, bytes32 addr) external payable returns (bytes memory); + + function buyName(bytes32 msgSender, uint256 msgValue, string memory name) external payable returns (bytes memory); + function fundName(bytes32 msgSender, uint256 msgValue, string memory name) + external + payable + returns (bytes memory); + function transferName(bytes32 msgSender, string memory name, uint256 toClusterId) + external + payable + returns (bytes memory); + function pokeName(string memory name) external payable returns (bytes memory); + + function bidName(bytes32 msgSender, uint256 msgValue, string memory name) external payable returns (bytes memory); + function reduceBid(bytes32 msgSender, string memory name, uint256 amount) external payable returns (bytes memory); + function acceptBid(bytes32 msgSender, string memory name) external payable returns (bytes memory); + function refundBid(bytes32 msgSender) external payable returns (bytes memory); + + function setDefaultClusterName(bytes32 msgSender, string memory name) external payable returns (bytes memory); + function setWalletName(bytes32 msgSender, bytes32 addr, string memory walletname) + external + payable + returns (bytes memory); +} From 1d2b67a979decc7cbdede87c10892ceec8c26ca4 Mon Sep 17 00:00:00 2001 From: Zodomo Date: Thu, 18 Jan 2024 15:37:00 -0600 Subject: [PATCH 2/5] fix tests by replacing erroneously deleted line --- src/ClustersHub.sol | 1 + src/ClustersSpoke.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ClustersHub.sol b/src/ClustersHub.sol index f554587..226c44c 100644 --- a/src/ClustersHub.sol +++ b/src/ClustersHub.sol @@ -180,6 +180,7 @@ contract ClustersHub is NameManagerHub { } function _hookCheck(uint256 clusterId) internal view override { + if (clusterId == 0) return; if (_verifiedAddresses[clusterId].length() == 0) revert Invalid(); } diff --git a/src/ClustersSpoke.sol b/src/ClustersSpoke.sol index 3f10730..3fd189c 100644 --- a/src/ClustersSpoke.sol +++ b/src/ClustersSpoke.sol @@ -117,6 +117,7 @@ contract ClustersSpoke is NameManagerSpoke { } function _hookCheck(uint256 clusterId) internal view override { + if (clusterId == 0) return; if (_verifiedAddresses[clusterId].length() == 0) revert Invalid(); } From 2e2197b4ed8ab1645f8582c45337bff0f782cedc Mon Sep 17 00:00:00 2001 From: Zodomo Date: Sun, 21 Jan 2024 18:37:07 -0600 Subject: [PATCH 3/5] redesigned deployment script --- script/Deploy.s.sol | 171 +++++++++++++++++++++++++++++++++++++++++++ script/Testnet.s.sol | 34 ++++++++- script/Testpad.s.sol | 51 +++++++++++++ 3 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 script/Deploy.s.sol create mode 100644 script/Testpad.s.sol diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..8c2afae --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Script, console2} from "forge-std/Script.sol"; +import {LibClone} from "solady/utils/LibClone.sol"; +import {LibString} from "solady/utils/LibString.sol"; +import {EnumerableSet} from "openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {Ownable} from "solady/auth/Ownable.sol"; +import {PricingFlat} from "../src/PricingFlat.sol"; +import {PricingHarberger} from "../src/PricingHarberger.sol"; +import {Endpoint} from "../src/Endpoint.sol"; +import {ClustersHub} from "../src/ClustersHub.sol"; +import {IPricing} from "../src/interfaces/IPricing.sol"; +import {IEndpoint} from "../src/interfaces/IEndpoint.sol"; +import {IOAppCore} from "layerzero-oapp/contracts/oapp/interfaces/IOAppCore.sol"; + +contract DeployScript is Script { + using LibString for string; + using LibString for uint256; + using LibString for address; + using EnumerableSet for EnumerableSet.UintSet; + + struct Deployment { + string chain; + address pricingTemplate; + address pricingProxy; + address endpointTemplate; + address endpointProxy; + address clusters; + address layerzero; + uint32 dstEid; + } + + uint256 internal deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address internal deployer = vm.addr(deployerPrivateKey); + address internal constant signer = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + + EnumerableSet.UintSet internal forkIds; + mapping(uint256 forkId => Deployment) internal deployments; + + uint32 internal constant LZ_EID_SEPOLIA = 40161; + uint32 internal constant LZ_EID_GOERLI = 40121; + uint32 internal constant LZ_EID_HOLESKY = 40217; + + address internal constant LZ_END_SEPOLIA = 0x464570adA09869d8741132183721B4f0769a0287; + address internal constant LZ_END_GOERLI = 0x464570adA09869d8741132183721B4f0769a0287; + address internal constant LZ_END_HOLESKY = 0x464570adA09869d8741132183721B4f0769a0287; + + function _addressToBytes32(address addr) internal pure returns (bytes32) { + return bytes32(uint256(uint160(addr))); + } + + function setUp() public {} + + function deploy( + string memory chain, + string memory rpcUrl, + bool isHarberger, + bool isHub, + uint32 dstEid, + address lzEndpoint + ) internal { + // Prep fork + uint256 forkId = vm.createFork(rpcUrl); + forkIds.add(forkId); + vm.selectFork(forkId); + vm.startBroadcast(deployerPrivateKey); + // Deploy and initialize pricing + if (isHub) { + if (isHarberger) { + PricingHarberger pricing = new PricingHarberger(); + deployments[forkId].pricingTemplate = address(pricing); + } else { + PricingFlat pricing = new PricingFlat(); + deployments[forkId].pricingTemplate = address(pricing); + } + vm.label(deployments[forkId].pricingTemplate, chain.concat(" Pricing Template")); + deployments[forkId].pricingProxy = LibClone.deployERC1967(deployments[forkId].pricingTemplate); + vm.label(deployments[forkId].pricingProxy, chain.concat(" Pricing Proxy")); + if (isHarberger) { + PricingHarberger(deployments[forkId].pricingProxy).initialize(deployer, block.timestamp); + } else { + PricingFlat(deployments[forkId].pricingProxy).initialize(deployer); + } + } + // Deploy and initialize endpoint + deployments[forkId].endpointTemplate = address(new Endpoint()); + vm.label(deployments[forkId].endpointTemplate, chain.concat(" Endpoint Template")); + deployments[forkId].endpointProxy = LibClone.deployERC1967(deployments[forkId].endpointTemplate); + vm.label(deployments[forkId].endpointProxy, chain.concat(" Endpoint Proxy")); + Endpoint(deployments[forkId].endpointProxy).initialize(deployer, signer, lzEndpoint); + if (isHub) { + deployments[forkId].clusters = address( + new ClustersHub( + deployments[forkId].pricingProxy, deployments[forkId].endpointProxy, block.timestamp + 5 minutes + ) + ); + vm.label(deployments[forkId].clusters, chain.concat(" Clusters Hub")); + IEndpoint(deployments[forkId].endpointProxy).setClustersAddr(deployments[forkId].clusters); + } else { + // Deploy Spoke infrastructure here + } + // Store remaining deployment information + deployments[forkId].chain = chain; + deployments[forkId].layerzero = lzEndpoint; + deployments[forkId].dstEid = dstEid; + vm.stopBroadcast(); + } + + function configure() internal { + uint256[] memory forks = forkIds.values(); + for (uint256 i; i < forks.length; ++i) { + address endpointProxy = deployments[i].endpointProxy; + vm.selectFork(forks[i]); + vm.startBroadcast(deployerPrivateKey); + for (uint256 j; j < forks.length; ++j) { + if (i == j) continue; + IOAppCore(endpointProxy).setPeer( + deployments[forks[j]].dstEid, _addressToBytes32(deployments[forks[j]].endpointProxy) + ); + } + if (i == 0) { + // Set dstEid on hub to enable replication + } else { + IEndpoint(endpointProxy).setDstEid(deployments[forks[0]].dstEid); + } + vm.stopBroadcast(); + } + } + + function run() public { + deploy("Sepolia", vm.envString("SEPOLIA_RPC_URL"), true, true, LZ_EID_SEPOLIA, LZ_END_SEPOLIA); + deploy("Goerli", vm.envString("GOERLI_RPC_URL"), true, false, LZ_EID_GOERLI, LZ_END_GOERLI); + deploy("Holesky", vm.envString("HOLESKY_RPC_URL"), true, false, LZ_EID_HOLESKY, LZ_END_HOLESKY); + configure(); + + for (uint256 i; i < forkIds.length(); ++i) { + Deployment memory deployment = deployments[forkIds.at(i)]; + if (deployment.pricingTemplate != address(0)) { + console2.log( + deployment.chain.concat(" Pricing Template: ").concat( + deployment.pricingTemplate.toHexStringChecksummed() + ) + ); + } + if (deployment.pricingProxy != address(0)) { + console2.log( + deployment.chain.concat(" Pricing Proxy: ").concat(deployment.pricingProxy.toHexStringChecksummed()) + ); + } + console2.log( + deployment.chain.concat(" Endpoint Template: ").concat( + deployment.endpointTemplate.toHexStringChecksummed() + ) + ); + console2.log( + deployment.chain.concat(" Endpoint Proxy: ").concat(deployment.endpointProxy.toHexStringChecksummed()) + ); + if (deployment.clusters != address(0)) { + console2.log( + deployment.chain.concat(" Clusters: ").concat(deployment.clusters.toHexStringChecksummed()) + ); + } + console2.log( + deployment.chain.concat(" LayerZero Endpoint: ").concat(deployment.layerzero.toHexStringChecksummed()) + ); + console2.log(deployment.chain.concat(" LayerZero DstEid: ").concat(uint256(deployment.dstEid).toString())); + console2.log(""); + } + } +} diff --git a/script/Testnet.s.sol b/script/Testnet.s.sol index 020c830..f2150b4 100644 --- a/script/Testnet.s.sol +++ b/script/Testnet.s.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.13; import {Script, console2} from "forge-std/Script.sol"; import {LibClone} from "solady/utils/LibClone.sol"; +import {Ownable} from "solady/auth/Ownable.sol"; import {PricingHarberger} from "../src/PricingHarberger.sol"; import {IPricing} from "../src/interfaces/IPricing.sol"; import {Endpoint} from "../src/Endpoint.sol"; @@ -48,7 +49,7 @@ contract TestnetScript is Script { vm.label(address(sepoliaPricing), "Sepolia Pricing Template"); IPricing sepoliaPricingProxy = IPricing(LibClone.deployERC1967(address(sepoliaPricing))); vm.label(address(sepoliaPricingProxy), "Sepolia Pricing Proxy"); - PricingHarberger(address(sepoliaPricingProxy)).initialize(msg.sender, block.timestamp + 7 days); + PricingHarberger(address(sepoliaPricingProxy)).initialize(msg.sender, block.timestamp + 1 minutes); Endpoint sepoliaEndpoint = new Endpoint(); vm.label(address(sepoliaEndpoint), "Sepolia Endpoint Template"); IEndpoint sepoliaEndpointProxy = IEndpoint(LibClone.deployERC1967(address(sepoliaEndpoint))); @@ -67,7 +68,7 @@ contract TestnetScript is Script { //vm.label(address(goerliPricing), "Goerli Pricing Template"); //IPricing goerliPricingProxy = IPricing(LibClone.deployERC1967(address(goerliPricing))); //vm.label(address(goerliPricingProxy), "Goerli Pricing Proxy"); - //PricingHarberger(address(goerliPricingProxy)).initialize(msg.sender, block.timestamp + 7 days); + //PricingHarberger(address(goerliPricingProxy)).initialize(msg.sender, block.timestamp + 1 minutes); Endpoint goerliEndpoint = new Endpoint(); vm.label(address(goerliEndpoint), "Goerli Endpoint Template"); IEndpoint goerliEndpointProxy = IEndpoint(LibClone.deployERC1967(address(goerliEndpoint))); @@ -88,7 +89,7 @@ contract TestnetScript is Script { //vm.label(address(holeskyPricing), "Holesky Pricing Template"); //IPricing holeskyPricingProxy = IPricing(LibClone.deployERC1967(address(holeskyPricing))); //vm.label(address(holeskyPricingProxy), "Holesky Pricing Proxy"); - //PricingHarberger(address(holeskyPricingProxy)).initialize(msg.sender, block.timestamp + 7 days); + //PricingHarberger(address(holeskyPricingProxy)).initialize(msg.sender, block.timestamp + 1 minutes); Endpoint holeskyEndpoint = new Endpoint(); vm.label(address(holeskyEndpoint), "Holesky Endpoint Template"); IEndpoint holeskyEndpointProxy = IEndpoint(LibClone.deployERC1967(address(holeskyEndpoint))); @@ -120,5 +121,32 @@ contract TestnetScript is Script { ); // No DstEid is set on Sepolia Hub as that engages replication, which is not yet fully implemented vm.stopBroadcast(); + + // Check state + vm.selectFork(sepoliaFork); + console2.log("Sepolia Pricing:"); + console2.log(address(sepoliaPricingProxy)); + console2.log("Sepolia Endpoint:"); + console2.log(address(sepoliaEndpointProxy)); + console2.log("Sepolia Clusters Hub:"); + console2.log(address(sepoliaClusters)); + //console2.log(sepoliaEndpointProxy.dstEid()); + //console2.log(Ownable(address(sepoliaEndpointProxy)).owner()); + //console2.logBytes32(IOAppCore(address(sepoliaEndpointProxy)).peers(LZ_EID_GOERLI)); + //console2.logBytes32(IOAppCore(address(sepoliaEndpointProxy)).peers(LZ_EID_HOLESKY)); + vm.selectFork(goerliFork); + console2.log("Goerli Endpoint:"); + console2.log(address(goerliEndpointProxy)); + //console2.log(goerliEndpointProxy.dstEid()); + //console2.log(Ownable(address(goerliEndpointProxy)).owner()); + //console2.logBytes32(IOAppCore(address(goerliEndpointProxy)).peers(LZ_EID_SEPOLIA)); + //console2.logBytes32(IOAppCore(address(goerliEndpointProxy)).peers(LZ_EID_HOLESKY)); + vm.selectFork(holeskyFork); + console2.log("Holesky Endpoint:"); + console2.log(address(holeskyEndpointProxy)); + //console2.log(holeskyEndpointProxy.dstEid()); + //console2.log(Ownable(address(holeskyEndpointProxy)).owner()); + //console2.logBytes32(IOAppCore(address(holeskyEndpointProxy)).peers(LZ_EID_SEPOLIA)); + //console2.logBytes32(IOAppCore(address(holeskyEndpointProxy)).peers(LZ_EID_GOERLI)); } } diff --git a/script/Testpad.s.sol b/script/Testpad.s.sol new file mode 100644 index 0000000..f4d7209 --- /dev/null +++ b/script/Testpad.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Script, console2} from "forge-std/Script.sol"; + +import {LibClone} from "solady/utils/LibClone.sol"; +import {Ownable} from "solady/auth/Ownable.sol"; +import {PricingHarberger} from "../src/PricingHarberger.sol"; +import {IPricing} from "../src/interfaces/IPricing.sol"; +import {Endpoint} from "../src/Endpoint.sol"; +import {IEndpoint} from "../src/interfaces/IEndpoint.sol"; +import {IOAppCore} from "layerzero-oapp/contracts/oapp/interfaces/IOAppCore.sol"; +import {ClustersHub} from "../src/ClustersHub.sol"; +import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; +import "../lib/LayerZero-v2/protocol/contracts/EndpointV2.sol"; + +contract TestpadScript is Script { + using OptionsBuilder for bytes; + + address internal constant HOLESKY_ENDPOINT = 0x27Ec7Bf5DdFac76f6c7A0EA7267d5b266D5Dd45C; + + string internal HOLESKY_RPC_URL = vm.envString("HOLESKY_RPC_URL"); + uint256 internal holeskyFork; + + /// @dev Convert address to bytes32 + function _addressToBytes32(address addr) internal pure returns (bytes32) { + return bytes32(uint256(uint160(addr))); + } + + function setUp() public { + holeskyFork = vm.createFork(HOLESKY_RPC_URL); + } + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + IEndpoint endpoint = IEndpoint(HOLESKY_ENDPOINT); + IOAppCore lzEndpoint = IOAppCore(HOLESKY_ENDPOINT); + + vm.selectFork(holeskyFork); + bytes memory data = abi.encodeWithSignature( + "buyName(bytes32,uint256,string)", _addressToBytes32(deployer), 0.01 ether, "zodomo" + ); + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000 gwei, uint128(0.01 ether)); + (uint256 nativeFee,) = endpoint.quote(40161, data, options, false); + console2.log(nativeFee); + console2.log(endpoint.dstEid()); + console2.log(Ownable(HOLESKY_ENDPOINT).owner()); + console2.logBytes32(lzEndpoint.peers(40161)); + } +} From 9b63a72b21c41945c87faadc3b8e73bc43ca2331 Mon Sep 17 00:00:00 2001 From: Zodomo Date: Sun, 21 Jan 2024 19:30:21 -0600 Subject: [PATCH 4/5] added config param for relay bridging --- src/ClustersHub.sol | 32 +++++++------ src/Endpoint.sol | 7 ++- src/NameManagerHub.sol | 82 ++++++++++++++++++++------------- src/NameManagerSpoke.sol | 2 +- src/interfaces/IClustersHub.sol | 74 ++++++++++++++++++++--------- src/interfaces/IEndpoint.sol | 5 +- 6 files changed, 132 insertions(+), 70 deletions(-) diff --git a/src/ClustersHub.sol b/src/ClustersHub.sol index 226c44c..dace041 100644 --- a/src/ClustersHub.sol +++ b/src/ClustersHub.sol @@ -32,7 +32,11 @@ contract ClustersHub is NameManagerHub { /// @dev For payable multicall to be secure, we cannot trust msg.value params in other external methods /// @dev Must instead do strict protocol invariant checking at the end of methods like Uniswap V2 - function multicall(bytes[] calldata data) external payable returns (bytes[] memory results) { + function multicall(bytes[] calldata data, bytes calldata config) + external + payable + returns (bytes[] memory results) + { _inMulticall = true; results = new bytes[](data.length); bool success; @@ -47,20 +51,20 @@ contract ClustersHub is NameManagerHub { _checkInvariant(); bytes memory payload = abi.encodeWithSignature("multicall(bytes[])", results); - IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); _inMulticall = false; } - function add(bytes32 addr) external payable returns (bytes memory payload) { - return add(_addressToBytes32(msg.sender), addr); + function add(bytes32 addr, bytes calldata config) external payable returns (bytes memory payload) { + return add(_addressToBytes32(msg.sender), addr, config); } - function verify(uint256 clusterId) external payable returns (bytes memory payload) { - return verify(_addressToBytes32(msg.sender), clusterId); + function verify(uint256 clusterId, bytes calldata config) external payable returns (bytes memory payload) { + return verify(_addressToBytes32(msg.sender), clusterId, config); } - function remove(bytes32 addr) external payable returns (bytes memory payload) { - return remove(_addressToBytes32(msg.sender), addr); + function remove(bytes32 addr, bytes calldata config) external payable returns (bytes memory payload) { + return remove(_addressToBytes32(msg.sender), addr, config); } function getUnverifiedAddresses(uint256 clusterId) external view returns (bytes32[] memory) { @@ -77,7 +81,7 @@ contract ClustersHub is NameManagerHub { /// ENDPOINT FUNCTIONS /// - function add(bytes32 msgSender, bytes32 addr) + function add(bytes32 msgSender, bytes32 addr, bytes calldata config) public payable onlyEndpoint(msgSender) @@ -90,10 +94,10 @@ contract ClustersHub is NameManagerHub { payload = abi.encodeWithSignature("add(bytes32,bytes32)", msgSender, addr); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); } - function verify(bytes32 msgSender, uint256 clusterId) + function verify(bytes32 msgSender, uint256 clusterId, bytes calldata config) public payable onlyEndpoint(msgSender) @@ -115,10 +119,10 @@ contract ClustersHub is NameManagerHub { payload = abi.encodeWithSignature("verify(bytes32,uint256)", msgSender, clusterId); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); } - function remove(bytes32 msgSender, bytes32 addr) + function remove(bytes32 msgSender, bytes32 addr, bytes calldata config) public payable onlyEndpoint(msgSender) @@ -134,7 +138,7 @@ contract ClustersHub is NameManagerHub { payload = abi.encodeWithSignature("remove(bytes32,bytes32)", msgSender, addr); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); } /// INTERNAL FUNCTIONS /// diff --git a/src/Endpoint.sol b/src/Endpoint.sol index ef4d629..482a0c2 100644 --- a/src/Endpoint.sol +++ b/src/Endpoint.sol @@ -276,7 +276,12 @@ contract Endpoint is OAppUpgradeable, UUPSUpgradeable, IEndpoint { lzTokenFee = msgQuote.lzTokenFee; } - function sendPayload(bytes calldata payload) external payable onlyClusters returns (bytes memory result) { + function sendPayload(bytes calldata payload, bytes calldata config) + external + payable + onlyClusters + returns (bytes memory result) + { // Short-circuit if dstEid isn't set for local-only functionality if (dstEid == 0) { IClustersHubEndpoint(clusters).noBridgeFundsReturn{value: msg.value}(); diff --git a/src/NameManagerHub.sol b/src/NameManagerHub.sol index e56353d..0a40714 100644 --- a/src/NameManagerHub.sol +++ b/src/NameManagerHub.sol @@ -130,13 +130,17 @@ abstract contract NameManagerHub is IClustersHub { /// @notice Buy unregistered name. Must pay at least minimum yearly payment /// @dev Processing is handled in overload - function buyName(uint256 msgValue, string memory name) external payable returns (bytes memory) { - buyName(_addressToBytes32(msg.sender), msgValue, name); + function buyName(uint256 msgValue, string memory name, bytes calldata config) + external + payable + returns (bytes memory) + { + buyName(_addressToBytes32(msg.sender), msgValue, name, config); return bytes(""); } /// @notice buyName() overload used by endpoint, msgSender must be msg.sender or endpoint - function buyName(bytes32 msgSender, uint256 msgValue, string memory name) + function buyName(bytes32 msgSender, uint256 msgValue, string memory name, bytes calldata config) public payable onlyEndpoint(msgSender) @@ -169,20 +173,24 @@ abstract contract NameManagerHub is IClustersHub { if (_inMulticall) { return payload; } else { - IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); _checkInvariant(); } } /// @notice Fund an existing and specific name, callable by anyone /// @dev Processing is handled in overload - function fundName(uint256 msgValue, string memory name) external payable returns (bytes memory) { - fundName(_addressToBytes32(msg.sender), msgValue, name); + function fundName(uint256 msgValue, string memory name, bytes calldata config) + external + payable + returns (bytes memory) + { + fundName(_addressToBytes32(msg.sender), msgValue, name, config); return bytes(""); } /// @notice fundName() overload used by endpoint, msgSender must be msg.sender or endpoint - function fundName(bytes32 msgSender, uint256 msgValue, string memory name) + function fundName(bytes32 msgSender, uint256 msgValue, string memory name, bytes calldata config) public payable onlyEndpoint(msgSender) @@ -199,20 +207,24 @@ abstract contract NameManagerHub is IClustersHub { if (_inMulticall) { return payload; } else { - IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); _checkInvariant(); } } /// @notice Move name from one cluster to another without payment /// @dev Processing is handled in overload - function transferName(string memory name, uint256 toClusterId) external payable returns (bytes memory) { - transferName(_addressToBytes32(msg.sender), name, toClusterId); + function transferName(string memory name, uint256 toClusterId, bytes calldata config) + external + payable + returns (bytes memory) + { + transferName(_addressToBytes32(msg.sender), name, toClusterId, config); return bytes(""); } /// @notice transferName() overload used by endpoint, msgSender must be msg.sender or endpoint - function transferName(bytes32 msgSender, string memory name, uint256 toClusterId) + function transferName(bytes32 msgSender, string memory name, uint256 toClusterId, bytes calldata config) public payable onlyEndpoint(msgSender) @@ -230,7 +242,7 @@ abstract contract NameManagerHub is IClustersHub { payload = abi.encodeWithSignature("transferName(bytes32,string,uint256)", msgSender, name, toClusterId); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); } /// @dev Transfer cluster name or delete cluster name without checking auth @@ -258,7 +270,7 @@ abstract contract NameManagerHub is IClustersHub { /// @notice Move amounts from ethBacked to protocolAccrual, and transfer names upon expiry to highest /// sufficient bidder. If no bids above yearly minimum, delete name registration. - function pokeName(string memory name) public payable returns (bytes memory payload) { + function pokeName(string memory name, bytes calldata config) public payable returns (bytes memory payload) { _checkNameValid(name); bytes32 _name = _stringToBytes32(name); if (nameToClusterId[_name] == 0) revert Unregistered(); @@ -296,7 +308,7 @@ abstract contract NameManagerHub is IClustersHub { payload = abi.encodeWithSignature("pokeName(string)", name); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); } /// @notice Place bids on valid names. Subsequent calls increases existing bid. If name is expired update ownership. @@ -305,13 +317,17 @@ abstract contract NameManagerHub is IClustersHub { /// @dev Should work smoothly for fully expired names and names partway through their duration /// @dev Needs to be onchain ETH bid escrowed in one place because otherwise prices shift /// @dev Processing is handled in overload - function bidName(uint256 msgValue, string memory name) external payable returns (bytes memory) { - bidName(_addressToBytes32(msg.sender), msgValue, name); + function bidName(uint256 msgValue, string memory name, bytes calldata config) + external + payable + returns (bytes memory) + { + bidName(_addressToBytes32(msg.sender), msgValue, name, config); return bytes(""); } /// @notice bidName() overload used in endpoint, msgSender must be msg.sender or endpoint - function bidName(bytes32 msgSender, uint256 msgValue, string memory name) + function bidName(bytes32 msgSender, uint256 msgValue, string memory name, bytes calldata config) public payable onlyEndpoint(msgSender) @@ -357,13 +373,13 @@ abstract contract NameManagerHub is IClustersHub { } } // Update name status and transfer to highest bidder if expired - pokeName(name); + this.pokeName(name, bytes("")); payload = abi.encodeWithSignature("bidName(bytes32,uint256,string)", msgSender, msgValue, name); if (_inMulticall) { return payload; } else { - IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); _checkInvariant(); } } @@ -393,7 +409,7 @@ abstract contract NameManagerHub is IClustersHub { if (amount > bid) amount = bid; // Poke name to update backing and ownership (if required) prior to bid adjustment - pokeName(name); + this.pokeName(name, bytes("")); // Skip bid reduction logic if pokeName() processed transfer to bidder due to name expiry if (bids[_name].ethAmount != 0) { @@ -424,7 +440,7 @@ abstract contract NameManagerHub is IClustersHub { payload = abi.encodeWithSignature("reduceBid(bytes32,string,uint256)", msgSender, name, amount); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, bytes("")); } /// @notice Accept bid and transfer name to bidder @@ -456,7 +472,7 @@ abstract contract NameManagerHub is IClustersHub { payload = abi.encodeWithSignature("acceptBid(bytes32,string)", msgSender, name); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, bytes("")); } /// @notice Allow failed bid refunds to be withdrawn @@ -477,21 +493,21 @@ abstract contract NameManagerHub is IClustersHub { payload = abi.encodeWithSignature("refundBid(bytes32)", msgSender); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, bytes("")); } /// LOCAL NAME MANAGEMENT /// /// @notice Set canonical name or erase it by setting "" /// @dev Processing is handled in overload - function setDefaultClusterName(string memory name) external payable returns (bytes memory) { - setDefaultClusterName(_addressToBytes32(msg.sender), name); + function setDefaultClusterName(string memory name, bytes calldata config) external payable returns (bytes memory) { + setDefaultClusterName(_addressToBytes32(msg.sender), name, config); return bytes(""); } /// @notice setDefaultClusterName() overload used by endpoint, msgSender must be msg.sender or endpoint. /// It is not possible to remove a name from a cluster entirely. A cluster must always have its default name - function setDefaultClusterName(bytes32 msgSender, string memory name) + function setDefaultClusterName(bytes32 msgSender, string memory name, bytes calldata config) public payable onlyEndpoint(msgSender) @@ -506,18 +522,22 @@ abstract contract NameManagerHub is IClustersHub { payload = abi.encodeWithSignature("setDefaultClusterName(bytes32,string)", msgSender, name); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); } /// @notice Set wallet name for msg.sender or erase it by setting "" /// @dev Processing is handled in overload - function setWalletName(bytes32 addr, string memory walletName) external payable returns (bytes memory) { - setWalletName(_addressToBytes32(msg.sender), addr, walletName); + function setWalletName(bytes32 addr, string memory walletName, bytes calldata config) + external + payable + returns (bytes memory) + { + setWalletName(_addressToBytes32(msg.sender), addr, walletName, config); return bytes(""); } /// @notice setWalletName() overload used by endpoint, msgSender must be msg.sender or endpoint - function setWalletName(bytes32 msgSender, bytes32 addr, string memory walletName) + function setWalletName(bytes32 msgSender, bytes32 addr, string memory walletName, bytes calldata config) public payable onlyEndpoint(msgSender) @@ -543,7 +563,7 @@ abstract contract NameManagerHub is IClustersHub { payload = abi.encodeWithSignature("setWalletName(bytes32,bytes32,string)", msgSender, addr, walletName); if (_inMulticall) return payload; - else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else IEndpoint(endpoint).sendPayload{value: msg.value}(payload, config); } /// @dev Set name-related state variables diff --git a/src/NameManagerSpoke.sol b/src/NameManagerSpoke.sol index 82d42dc..aa02568 100644 --- a/src/NameManagerSpoke.sol +++ b/src/NameManagerSpoke.sol @@ -221,7 +221,7 @@ abstract contract NameManagerSpoke is IClustersSpoke, Ownable { if (msg.sender != endpoint) { payload = abi.encodeWithSignature("pokeName(string)", name); if (_inMulticall) return payload; - else return IEndpoint(endpoint).sendPayload{value: msg.value}(payload); + else return IEndpoint(endpoint).sendPayload{value: msg.value}(payload, bytes("")); } else { bytes32 _name = _stringToBytes32(name); IClustersSpoke.PriceIntegral memory integral = priceIntegral[_name]; diff --git a/src/interfaces/IClustersHub.sol b/src/interfaces/IClustersHub.sol index 07f134d..99d2f5d 100644 --- a/src/interfaces/IClustersHub.sol +++ b/src/interfaces/IClustersHub.sol @@ -82,31 +82,55 @@ interface IClustersHub { /// EXTERNAL FUNCTIONS /// - function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); - - function add(bytes32 addr) external payable returns (bytes memory); - function add(bytes32 msgSender, bytes32 addr) external payable returns (bytes memory); - function verify(uint256 clusterId) external payable returns (bytes memory); - function verify(bytes32 msgSender, uint256 clusterId) external payable returns (bytes memory); - function remove(bytes32 addr) external payable returns (bytes memory); - function remove(bytes32 msgSender, bytes32 addr) external payable returns (bytes memory); - - function buyName(uint256 msgValue, string memory name) external payable returns (bytes memory); - function buyName(bytes32 msgSender, uint256 msgValue, string memory name) external payable returns (bytes memory); - function fundName(uint256 msgValue, string memory name) external payable returns (bytes memory); - function fundName(bytes32 msgSender, uint256 msgValue, string memory name) + function multicall(bytes[] calldata data, bytes calldata config) + external + payable + returns (bytes[] memory results); + + function add(bytes32 addr, bytes calldata config) external payable returns (bytes memory); + function add(bytes32 msgSender, bytes32 addr, bytes calldata config) external payable returns (bytes memory); + function verify(uint256 clusterId, bytes calldata config) external payable returns (bytes memory); + function verify(bytes32 msgSender, uint256 clusterId, bytes calldata config) + external + payable + returns (bytes memory); + function remove(bytes32 addr, bytes calldata config) external payable returns (bytes memory); + function remove(bytes32 msgSender, bytes32 addr, bytes calldata config) external payable returns (bytes memory); + + function buyName(uint256 msgValue, string memory name, bytes calldata config) + external + payable + returns (bytes memory); + function buyName(bytes32 msgSender, uint256 msgValue, string memory name, bytes calldata config) + external + payable + returns (bytes memory); + function fundName(uint256 msgValue, string memory name, bytes calldata config) + external + payable + returns (bytes memory); + function fundName(bytes32 msgSender, uint256 msgValue, string memory name, bytes calldata config) external payable returns (bytes memory); - function transferName(string memory name, uint256 toClusterId) external payable returns (bytes memory); - function transferName(bytes32 msgSender, string memory name, uint256 toClusterId) + function transferName(string memory name, uint256 toClusterId, bytes calldata config) external payable returns (bytes memory); - function pokeName(string memory name) external payable returns (bytes memory); + function transferName(bytes32 msgSender, string memory name, uint256 toClusterId, bytes calldata config) + external + payable + returns (bytes memory); + function pokeName(string memory name, bytes calldata config) external payable returns (bytes memory); - function bidName(uint256 msgValue, string memory name) external payable returns (bytes memory); - function bidName(bytes32 msgSender, uint256 msgValue, string memory name) external payable returns (bytes memory); + function bidName(uint256 msgValue, string memory name, bytes calldata config) + external + payable + returns (bytes memory); + function bidName(bytes32 msgSender, uint256 msgValue, string memory name, bytes calldata config) + external + payable + returns (bytes memory); function reduceBid(string memory name, uint256 amount) external payable returns (bytes memory); function reduceBid(bytes32 msgSender, string memory name, uint256 amount) external payable returns (bytes memory); function acceptBid(string memory name) external payable returns (bytes memory); @@ -114,10 +138,16 @@ interface IClustersHub { function refundBid() external payable returns (bytes memory); function refundBid(bytes32 msgSender) external payable returns (bytes memory); - function setDefaultClusterName(string memory name) external payable returns (bytes memory); - function setDefaultClusterName(bytes32 msgSender, string memory name) external payable returns (bytes memory); - function setWalletName(bytes32 addr, string memory walletname) external payable returns (bytes memory); - function setWalletName(bytes32 msgSender, bytes32 addr, string memory walletname) + function setDefaultClusterName(string memory name, bytes calldata config) external payable returns (bytes memory); + function setDefaultClusterName(bytes32 msgSender, string memory name, bytes calldata config) + external + payable + returns (bytes memory); + function setWalletName(bytes32 addr, string memory walletname, bytes calldata config) + external + payable + returns (bytes memory); + function setWalletName(bytes32 msgSender, bytes32 addr, string memory walletname, bytes calldata config) external payable returns (bytes memory); diff --git a/src/interfaces/IEndpoint.sol b/src/interfaces/IEndpoint.sol index 0b15b70..6a93ad9 100644 --- a/src/interfaces/IEndpoint.sol +++ b/src/interfaces/IEndpoint.sol @@ -79,7 +79,10 @@ interface IEndpoint { external returns (uint256 nativeFee, uint256 lzTokenFee); - function sendPayload(bytes calldata payload) external payable returns (bytes memory result); + function sendPayload(bytes calldata payload, bytes calldata config) + external + payable + returns (bytes memory result); function lzSend(bytes memory data, bytes memory options, address refundAddress) external payable From 74ef3b0bb0570e7bf0b69f6fcea8bf1fbb3136e4 Mon Sep 17 00:00:00 2001 From: Zodomo Date: Mon, 22 Jan 2024 15:16:09 -0600 Subject: [PATCH 5/5] temp relocated tests for compiling, refactored contracts to support passing LZ config --- src/Endpoint.sol | 40 ++++++++++++----- src/interfaces/IEndpoint.sol | 2 +- test/GasBenchmark.t.sol | 40 ++++++++--------- .../concrete/outbound/Clusters_buyName.t.sol | 39 ++++++++++++++++ .../outbound/Endpoint_gasAirdrop.t.sol | 4 +- .../shared/SharedOutboundHarbergerTest.t.sol | 44 +++++++++++++++++++ .../local/concrete/Clusters_acceptBid.t.sol | 0 .../unit/local/concrete/Clusters_add.t.sol | 0 .../local/concrete/Clusters_bidName.t.sol | 0 .../local/concrete/Clusters_buyName.t.sol | 0 .../local/concrete/Clusters_fundName.t.sol | 0 .../local/concrete/Clusters_multicall.t.sol | 0 .../local/concrete/Clusters_pokeName.t.sol | 0 .../local/concrete/Clusters_reduceBid.t.sol | 0 .../local/concrete/Clusters_refundBid.t.sol | 0 .../unit/local/concrete/Clusters_remove.t.sol | 0 .../Clusters_setDefaultClusterName.t.sol | 0 .../concrete/Clusters_setWalletName.t.sol | 0 .../concrete/Clusters_transferName.t.sol | 0 .../unit/local/concrete/Clusters_verify.t.sol | 0 .../unit/local/concrete/Endpoint_ECDSA.t.sol | 0 .../concrete/Endpoint_fulfillOrder.t.sol | 0 .../local/concrete/Endpoint_multicall.t.sol | 0 .../concrete/Endpoint_setClustersAddr.t.sol | 0 .../concrete/Endpoint_setSignerAddr.t.sol | 0 .../concrete/Endpoint_upgradeToAndCall.t.sol | 0 .../local/concrete/PricingHarberger.t.sol | 0 .../concrete/Pricing_upgradeToAndCall.t.sol | 0 .../unit/local/shared/SharedPricingFlat.t.sol | 0 .../local/shared/SharedPricingHarberger.t.sol | 0 .../concrete/inbound/Endpoint_add.t.sol | 0 .../concrete/inbound/Endpoint_bidName.t.sol | 0 .../concrete/inbound/Endpoint_buyName.t.sol | 0 .../concrete/inbound/Endpoint_fundName.t.sol | 0 .../inbound/Endpoint_gasAirdrop.t.sol | 0 .../concrete/inbound/Endpoint_pokeName.t.sol | 0 .../concrete/inbound/Endpoint_refund.t.sol | 0 .../concrete/inbound/Endpoint_remove.t.sol | 0 .../Endpoint_setDefaultClusterName.t.sol | 0 .../inbound/Endpoint_setWalletName.t.sol | 0 .../inbound/Endpoint_transferName.t.sol | 0 .../concrete/inbound/Endpoint_verify.t.sol | 0 42 files changed, 134 insertions(+), 35 deletions(-) create mode 100644 test/unit/remote/concrete/outbound/Clusters_buyName.t.sol create mode 100644 test/unit/remote/shared/SharedOutboundHarbergerTest.t.sol rename {test => working/test}/unit/local/concrete/Clusters_acceptBid.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_add.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_bidName.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_buyName.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_fundName.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_multicall.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_pokeName.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_reduceBid.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_refundBid.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_remove.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_setDefaultClusterName.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_setWalletName.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_transferName.t.sol (100%) rename {test => working/test}/unit/local/concrete/Clusters_verify.t.sol (100%) rename {test => working/test}/unit/local/concrete/Endpoint_ECDSA.t.sol (100%) rename {test => working/test}/unit/local/concrete/Endpoint_fulfillOrder.t.sol (100%) rename {test => working/test}/unit/local/concrete/Endpoint_multicall.t.sol (100%) rename {test => working/test}/unit/local/concrete/Endpoint_setClustersAddr.t.sol (100%) rename {test => working/test}/unit/local/concrete/Endpoint_setSignerAddr.t.sol (100%) rename {test => working/test}/unit/local/concrete/Endpoint_upgradeToAndCall.t.sol (100%) rename {test => working/test}/unit/local/concrete/PricingHarberger.t.sol (100%) rename {test => working/test}/unit/local/concrete/Pricing_upgradeToAndCall.t.sol (100%) rename {test => working/test}/unit/local/shared/SharedPricingFlat.t.sol (100%) rename {test => working/test}/unit/local/shared/SharedPricingHarberger.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_add.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_bidName.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_buyName.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_fundName.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_gasAirdrop.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_pokeName.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_refund.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_remove.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_setDefaultClusterName.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_setWalletName.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_transferName.t.sol (100%) rename {test => working/test}/unit/remote/concrete/inbound/Endpoint_verify.t.sol (100%) diff --git a/src/Endpoint.sol b/src/Endpoint.sol index 482a0c2..528e8a6 100644 --- a/src/Endpoint.sol +++ b/src/Endpoint.sol @@ -11,12 +11,12 @@ import {console2} from "forge-std/Test.sol"; interface IClustersHubEndpoint { function noBridgeFundsReturn() external payable; - function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); + function multicall(bytes[] calldata data, bytes calldata config) external payable returns (bytes[] memory results); - function buyName(bytes32 msgSender, uint256 msgValue, string memory name) external payable; + function buyName(bytes32 msgSender, uint256 msgValue, string memory name, bytes calldata config) external payable; function bids(bytes32 name) external view returns (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder); - function bidName(bytes32 msgSender, uint256 msgValue, string memory name) external payable; + function bidName(bytes32 msgSender, uint256 msgValue, string memory name, bytes calldata config) external payable; function acceptBid(bytes32 msgSender, string memory name) external payable returns (uint256 bidAmount); } @@ -94,6 +94,21 @@ contract Endpoint is OAppUpgradeable, UUPSUpgradeable, IEndpoint { } } + /// @dev Returns LZ config params + function _decodeLzConfig(bytes calldata config) internal pure returns (MessagingFee memory fee, address refundAddress, bytes memory options) { + (fee.nativeFee, fee.lzTokenFee) = abi.decode(config[0:64], (uint256, uint256)); + uint256 optionsLength; + assembly { + calldatacopy(add(0x20, 0), 64, 20) + refundAddress := mload(0x20) + optionsLength := calldataload(84) + } + options = new bytes(optionsLength); + assembly { + calldatacopy(add(options, 32), add(84, 32), optionsLength) + } + } + /// @dev Returns true if msgSender in the provided calldata matches a particular address, true for pokeName() function _validateMsgSender(bytes32 msgSender, bytes memory data) internal pure returns (bool) { if (_getFuncSelector(data) == POKE_NAME_SELECTOR) return true; @@ -164,7 +179,7 @@ contract Endpoint is OAppUpgradeable, UUPSUpgradeable, IEndpoint { /// PERMISSIONED FUNCTIONS /// - function multicall(bytes[] calldata data, bytes calldata sig) external payable returns (bytes[] memory results) { + function multicall(bytes[] calldata data, bytes calldata sig, bytes calldata config) external payable returns (bytes[] memory results) { // Validate signature if (!verifyMulticall(data, sig)) revert ECDSA.InvalidSignature(); @@ -196,10 +211,10 @@ contract Endpoint is OAppUpgradeable, UUPSUpgradeable, IEndpoint { } } // Send remainder of msg.value to Clusters - results = IClustersHubEndpoint(clusters).multicall{value: msg.value - endpointFunds}(_data); + results = IClustersHubEndpoint(clusters).multicall{value: msg.value - endpointFunds}(_data, config); } else { // If no calls to Endpoint, pass the calldata directly to ClustersHub - results = IClustersHubEndpoint(clusters).multicall{value: msg.value}(data); + results = IClustersHubEndpoint(clusters).multicall{value: msg.value}(data, config); } } @@ -217,7 +232,7 @@ contract Endpoint is OAppUpgradeable, UUPSUpgradeable, IEndpoint { if (msg.value < msgValue || msgValue <= bidAmount) revert Insufficient(); if (!isValid) revert Invalid(); - IClustersHubEndpoint(clusters).bidName{value: msg.value}(_addressToBytes32(msg.sender), msg.value, name); + IClustersHubEndpoint(clusters).bidName{value: msg.value}(_addressToBytes32(msg.sender), msg.value, name, bytes("")); { bytes32 originatorBytes = _addressToBytes32(originator); IClustersHubEndpoint(clusters).acceptBid{value: 0}(originatorBytes, name); @@ -287,11 +302,12 @@ contract Endpoint is OAppUpgradeable, UUPSUpgradeable, IEndpoint { IClustersHubEndpoint(clusters).noBridgeFundsReturn{value: msg.value}(); return bytes(""); } - /*// TODO: Figure out how to assign these - bytes memory options; - MessagingFee memory fee; - address refundAddress; - result = abi.encode(_lzSend(dstEid, payload, options, fee, refundAddress));*/ + // Potentially remove this if desired so replication cannot be skipped, mainly for testing + if (config.length > 0) { + // Route payload to relay spoke + (MessagingFee memory fee, address refundAddress, bytes memory options) = _decodeLzConfig(config); + result = abi.encode(_lzSend(dstEid, payload, options, fee, refundAddress)); + } } function selfClustersCall(bytes memory data) public payable { diff --git a/src/interfaces/IEndpoint.sol b/src/interfaces/IEndpoint.sol index 6a93ad9..5371a5e 100644 --- a/src/interfaces/IEndpoint.sol +++ b/src/interfaces/IEndpoint.sol @@ -55,7 +55,7 @@ interface IEndpoint { /// PERMISSIONED FUNCTIONS /// - function multicall(bytes[] calldata data, bytes calldata sig) external payable returns (bytes[] memory results); + function multicall(bytes[] calldata data, bytes calldata sig, bytes calldata config) external payable returns (bytes[] memory results); function fulfillOrder( uint256 msgValue, uint256 nonce, diff --git a/test/GasBenchmark.t.sol b/test/GasBenchmark.t.sol index 142272d..fc32cc8 100644 --- a/test/GasBenchmark.t.sol +++ b/test/GasBenchmark.t.sol @@ -12,10 +12,10 @@ contract GasBenchmarkTest is Base_Test { function testBenchmark() public { bytes[] memory buyBatchData = new bytes[](2); buyBatchData[0] = abi.encodeWithSignature( - "buyName(bytes32,uint256,string)", users.alicePrimary, minPrice, constants.TEST_NAME() + "buyName(bytes32,uint256,string,bytes)", users.alicePrimary, minPrice, constants.TEST_NAME(), bytes("") ); buyBatchData[1] = - abi.encodeWithSignature("buyName(bytes32,uint256,string)", users.alicePrimary, minPrice, "zodomo"); + abi.encodeWithSignature("buyName(bytes32,uint256,string,bytes)", users.alicePrimary, minPrice, "zodomo", bytes("")); vm.startPrank(users.signer); bytes32 messageHash = endpointProxy.getMulticallHash(buyBatchData); @@ -25,43 +25,43 @@ contract GasBenchmarkTest is Base_Test { vm.stopPrank(); vm.startPrank(users.alicePrimary); - endpointProxy.multicall{value: 2 * minPrice}(buyBatchData, sig1); - clusters.fundName{value: 0.5 ether}(0.5 ether, constants.TEST_NAME()); - clusters.add(_addressToBytes32(users.aliceSecondary)); - clusters.setDefaultClusterName("zodomo"); - clusters.setWalletName(_addressToBytes32(users.alicePrimary), "Primary"); + endpointProxy.multicall{value: 2 * minPrice}(buyBatchData, sig1, bytes("")); + clusters.fundName{value: 0.5 ether}(0.5 ether, constants.TEST_NAME(), bytes("")); + clusters.add(_addressToBytes32(users.aliceSecondary), bytes("")); + clusters.setDefaultClusterName("zodomo", bytes("")); + clusters.setWalletName(_addressToBytes32(users.alicePrimary), "Primary", bytes("")); vm.stopPrank(); vm.prank(users.aliceSecondary); - clusters.verify(1); + clusters.verify(1, bytes("")); vm.startPrank(users.alicePrimary); bytes[] memory data = new bytes[](5); - data[0] = abi.encodeWithSignature("fundName(uint256,string)", 0.5 ether, constants.TEST_NAME()); - data[1] = abi.encodeWithSignature("fundName(uint256,string)", 1 ether, "zodomo"); - data[2] = abi.encodeWithSignature("setDefaultClusterName(string)", constants.TEST_NAME()); + data[0] = abi.encodeWithSignature("fundName(uint256,string,bytes)", 0.5 ether, constants.TEST_NAME(), bytes("")); + data[1] = abi.encodeWithSignature("fundName(uint256,string,bytes)", 1 ether, "zodomo", bytes("")); + data[2] = abi.encodeWithSignature("setDefaultClusterName(string,bytes)", constants.TEST_NAME(), bytes("")); data[3] = - abi.encodeWithSignature("setWalletName(bytes32,string)", _addressToBytes32(users.alicePrimary), "Main"); + abi.encodeWithSignature("setWalletName(bytes32,string,bytes)", _addressToBytes32(users.alicePrimary), "Main", bytes("")); data[4] = abi.encodeWithSignature( - "setWalletName(bytes32,string)", _addressToBytes32(users.aliceSecondary), "Secondary" + "setWalletName(bytes32,string,bytes)", _addressToBytes32(users.aliceSecondary), "Secondary", bytes("") ); - clusters.multicall{value: minPrice + 1.5 ether}(data); - clusters.remove(_addressToBytes32(users.aliceSecondary)); + clusters.multicall{value: minPrice + 1.5 ether}(data, bytes("")); + clusters.remove(_addressToBytes32(users.aliceSecondary), bytes("")); vm.stopPrank(); vm.startPrank(users.bidder); - clusters.bidName{value: 2 ether}(2 ether, constants.TEST_NAME()); + clusters.bidName{value: 2 ether}(2 ether, constants.TEST_NAME(), bytes("")); vm.warp(constants.START_TIME() + 14 days); - clusters.pokeName(constants.TEST_NAME()); + clusters.pokeName(constants.TEST_NAME(), bytes("")); vm.warp(constants.START_TIME() + 31 days); clusters.reduceBid(constants.TEST_NAME(), 1 ether); vm.stopPrank(); vm.startPrank(users.alicePrimary); - clusters.buyName{value: minPrice}(minPrice, "burned"); - clusters.transferName("burned", 0); + clusters.buyName{value: minPrice}(minPrice, "burned", bytes("")); + clusters.transferName("burned", 0, bytes("")); clusters.acceptBid(constants.TEST_NAME()); - clusters.transferName("zodomo", 2); + clusters.transferName("zodomo", 2, bytes("")); vm.stopPrank(); } } diff --git a/test/unit/remote/concrete/outbound/Clusters_buyName.t.sol b/test/unit/remote/concrete/outbound/Clusters_buyName.t.sol new file mode 100644 index 0000000..ec23118 --- /dev/null +++ b/test/unit/remote/concrete/outbound/Clusters_buyName.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Outbound_Harberger_Shared_Test, console2, MessagingFee} from "../../shared/SharedOutboundHarbergerTest.t.sol"; +import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; + +contract Outbound_Endpoint_gasAirdrop_Unit_Concrete_Test is Outbound_Harberger_Shared_Test { + using OptionsBuilder for bytes; + + function testBuyName__() public { + vm.startPrank(users.alicePrimary); + // Prepare relay payload information + bytes memory relayData = abi.encodeWithSignature( + "buyName(bytes32,uint256,string)", _addressToBytes32(users.alicePrimary), minPrice, constants.TEST_NAME() + ); + bytes memory relayOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000 gwei, uint128(0)); + (uint256 relayNativeFee,) = localEndpoint.quote(2, relayData, relayOptions, false); + // Prepare initiator payload information + bytes memory data = abi.encodeWithSignature( + "buyName(bytes32,uint256,string,bytes)", _addressToBytes32(users.alicePrimary), minPrice, constants.TEST_NAME(), relayOptions + ); + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000 gwei, uint128(minPrice + relayNativeFee)); + (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); + remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); + verifyPackets(1, address(localEndpoint)); + vm.stopPrank(); + + bytes32[] memory unverified; + bytes32[] memory verified = new bytes32[](1); + verified[0] = _addressToBytes32(users.alicePrimary); + bytes32[] memory names = new bytes32[](1); + names[0] = _stringToBytes32(constants.TEST_NAME()); + assertBalances(1, minPrice, 0, minPrice, 0); + assertNameBacking(1, constants.TEST_NAME(), minPrice); + assertUnverifiedAddresses(1, 1, 0, unverified); + assertVerifiedAddresses(1, 1, 1, verified); + assertClusterNames(1, 1, 1, names); + } +} \ No newline at end of file diff --git a/test/unit/remote/concrete/outbound/Endpoint_gasAirdrop.t.sol b/test/unit/remote/concrete/outbound/Endpoint_gasAirdrop.t.sol index 4a360c9..0d0d62c 100644 --- a/test/unit/remote/concrete/outbound/Endpoint_gasAirdrop.t.sol +++ b/test/unit/remote/concrete/outbound/Endpoint_gasAirdrop.t.sol @@ -31,7 +31,7 @@ contract Outbound_Endpoint_gasAirdrop_Unit_Concrete_Test is Inbound_Harberger_Sh (uint256 airdropFee,) = localEndpoint.quote(2, bytes(""), options, false); uint256 balance = address(users.aliceSecondary).balance; bytes[] memory data = new bytes[](2); - data[0] = abi.encodeWithSignature("buyName(bytes32,uint256,string)", caller, minPrice, testName); + data[0] = abi.encodeWithSignature("buyName(bytes32,uint256,string,bytes)", caller, minPrice, testName, bytes("")); data[1] = abi.encodeWithSignature("gasAirdrop(uint256,uint32,bytes)", airdropFee, 2, options); vm.stopPrank(); @@ -44,7 +44,7 @@ contract Outbound_Endpoint_gasAirdrop_Unit_Concrete_Test is Inbound_Harberger_Sh vm.prank(users.alicePrimary); console2.logBytes(options); - localEndpoint.multicall{value: airdropFee + minPrice}(data, sig); + localEndpoint.multicall{value: airdropFee + minPrice}(data, sig, bytes("")); verifyPackets(2, address(remoteEndpoint)); assertEq(balance + minPrice, address(users.aliceSecondary).balance, "airdrop balance error"); } diff --git a/test/unit/remote/shared/SharedOutboundHarbergerTest.t.sol b/test/unit/remote/shared/SharedOutboundHarbergerTest.t.sol new file mode 100644 index 0000000..478d72f --- /dev/null +++ b/test/unit/remote/shared/SharedOutboundHarbergerTest.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Base_Test, ClustersHub, Endpoint, OAppUpgradeable, EnumerableSet, console2} from "../../../Base.t.sol"; +import {MessagingFee} from "layerzero-oapp/contracts/oapp-upgradeable/OAppUpgradeable.sol"; + +abstract contract Outbound_Harberger_Shared_Test is Base_Test { + using EnumerableSet for EnumerableSet.AddressSet; + + Endpoint internal localEndpoint; + Endpoint internal remoteEndpoint; + ClustersHub internal localClusters; + + function setUp() public virtual override { + Base_Test.setUp(); + configureHarbergerEnvironment(2); + vm.startPrank(users.clustersAdmin); + Endpoint(endpointGroup.at(0)).setDstEid(2); + vm.stopPrank(); + localEndpoint = Endpoint(endpointGroup.at(0)); + remoteEndpoint = Endpoint(endpointGroup.at(1)); + localClusters = ClustersHub(clustersGroup.at(0)); + console2.log("localClusters address:"); + console2.log(address(localClusters)); + console2.log(""); + console2.log("localEndpoint address:"); + console2.log(address(localEndpoint)); + console2.log("localEndpoint's L0 Endpoint:"); + console2.log(address(OAppUpgradeable(localEndpoint).endpoint())); + console2.log("localEndpoint's L0 EID:"); + console2.log((OAppUpgradeable(localEndpoint).endpoint()).eid()); + console2.log("localEndpoint's DstEid: (will be 0 as it doesn't send to a destination yet)"); + console2.log(localEndpoint.dstEid()); + console2.log(""); + console2.log("remoteEndpoint address:"); + console2.log(address(remoteEndpoint)); + console2.log("remoteEndpoint's L0 Endpoint:"); + console2.log(address(OAppUpgradeable(remoteEndpoint).endpoint())); + console2.log("remoteEndpoint's L0 EID:"); + console2.log((OAppUpgradeable(remoteEndpoint).endpoint()).eid()); + console2.log("remoteEndpoint's DstEid:"); + console2.log(remoteEndpoint.dstEid()); + } +} diff --git a/test/unit/local/concrete/Clusters_acceptBid.t.sol b/working/test/unit/local/concrete/Clusters_acceptBid.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_acceptBid.t.sol rename to working/test/unit/local/concrete/Clusters_acceptBid.t.sol diff --git a/test/unit/local/concrete/Clusters_add.t.sol b/working/test/unit/local/concrete/Clusters_add.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_add.t.sol rename to working/test/unit/local/concrete/Clusters_add.t.sol diff --git a/test/unit/local/concrete/Clusters_bidName.t.sol b/working/test/unit/local/concrete/Clusters_bidName.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_bidName.t.sol rename to working/test/unit/local/concrete/Clusters_bidName.t.sol diff --git a/test/unit/local/concrete/Clusters_buyName.t.sol b/working/test/unit/local/concrete/Clusters_buyName.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_buyName.t.sol rename to working/test/unit/local/concrete/Clusters_buyName.t.sol diff --git a/test/unit/local/concrete/Clusters_fundName.t.sol b/working/test/unit/local/concrete/Clusters_fundName.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_fundName.t.sol rename to working/test/unit/local/concrete/Clusters_fundName.t.sol diff --git a/test/unit/local/concrete/Clusters_multicall.t.sol b/working/test/unit/local/concrete/Clusters_multicall.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_multicall.t.sol rename to working/test/unit/local/concrete/Clusters_multicall.t.sol diff --git a/test/unit/local/concrete/Clusters_pokeName.t.sol b/working/test/unit/local/concrete/Clusters_pokeName.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_pokeName.t.sol rename to working/test/unit/local/concrete/Clusters_pokeName.t.sol diff --git a/test/unit/local/concrete/Clusters_reduceBid.t.sol b/working/test/unit/local/concrete/Clusters_reduceBid.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_reduceBid.t.sol rename to working/test/unit/local/concrete/Clusters_reduceBid.t.sol diff --git a/test/unit/local/concrete/Clusters_refundBid.t.sol b/working/test/unit/local/concrete/Clusters_refundBid.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_refundBid.t.sol rename to working/test/unit/local/concrete/Clusters_refundBid.t.sol diff --git a/test/unit/local/concrete/Clusters_remove.t.sol b/working/test/unit/local/concrete/Clusters_remove.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_remove.t.sol rename to working/test/unit/local/concrete/Clusters_remove.t.sol diff --git a/test/unit/local/concrete/Clusters_setDefaultClusterName.t.sol b/working/test/unit/local/concrete/Clusters_setDefaultClusterName.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_setDefaultClusterName.t.sol rename to working/test/unit/local/concrete/Clusters_setDefaultClusterName.t.sol diff --git a/test/unit/local/concrete/Clusters_setWalletName.t.sol b/working/test/unit/local/concrete/Clusters_setWalletName.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_setWalletName.t.sol rename to working/test/unit/local/concrete/Clusters_setWalletName.t.sol diff --git a/test/unit/local/concrete/Clusters_transferName.t.sol b/working/test/unit/local/concrete/Clusters_transferName.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_transferName.t.sol rename to working/test/unit/local/concrete/Clusters_transferName.t.sol diff --git a/test/unit/local/concrete/Clusters_verify.t.sol b/working/test/unit/local/concrete/Clusters_verify.t.sol similarity index 100% rename from test/unit/local/concrete/Clusters_verify.t.sol rename to working/test/unit/local/concrete/Clusters_verify.t.sol diff --git a/test/unit/local/concrete/Endpoint_ECDSA.t.sol b/working/test/unit/local/concrete/Endpoint_ECDSA.t.sol similarity index 100% rename from test/unit/local/concrete/Endpoint_ECDSA.t.sol rename to working/test/unit/local/concrete/Endpoint_ECDSA.t.sol diff --git a/test/unit/local/concrete/Endpoint_fulfillOrder.t.sol b/working/test/unit/local/concrete/Endpoint_fulfillOrder.t.sol similarity index 100% rename from test/unit/local/concrete/Endpoint_fulfillOrder.t.sol rename to working/test/unit/local/concrete/Endpoint_fulfillOrder.t.sol diff --git a/test/unit/local/concrete/Endpoint_multicall.t.sol b/working/test/unit/local/concrete/Endpoint_multicall.t.sol similarity index 100% rename from test/unit/local/concrete/Endpoint_multicall.t.sol rename to working/test/unit/local/concrete/Endpoint_multicall.t.sol diff --git a/test/unit/local/concrete/Endpoint_setClustersAddr.t.sol b/working/test/unit/local/concrete/Endpoint_setClustersAddr.t.sol similarity index 100% rename from test/unit/local/concrete/Endpoint_setClustersAddr.t.sol rename to working/test/unit/local/concrete/Endpoint_setClustersAddr.t.sol diff --git a/test/unit/local/concrete/Endpoint_setSignerAddr.t.sol b/working/test/unit/local/concrete/Endpoint_setSignerAddr.t.sol similarity index 100% rename from test/unit/local/concrete/Endpoint_setSignerAddr.t.sol rename to working/test/unit/local/concrete/Endpoint_setSignerAddr.t.sol diff --git a/test/unit/local/concrete/Endpoint_upgradeToAndCall.t.sol b/working/test/unit/local/concrete/Endpoint_upgradeToAndCall.t.sol similarity index 100% rename from test/unit/local/concrete/Endpoint_upgradeToAndCall.t.sol rename to working/test/unit/local/concrete/Endpoint_upgradeToAndCall.t.sol diff --git a/test/unit/local/concrete/PricingHarberger.t.sol b/working/test/unit/local/concrete/PricingHarberger.t.sol similarity index 100% rename from test/unit/local/concrete/PricingHarberger.t.sol rename to working/test/unit/local/concrete/PricingHarberger.t.sol diff --git a/test/unit/local/concrete/Pricing_upgradeToAndCall.t.sol b/working/test/unit/local/concrete/Pricing_upgradeToAndCall.t.sol similarity index 100% rename from test/unit/local/concrete/Pricing_upgradeToAndCall.t.sol rename to working/test/unit/local/concrete/Pricing_upgradeToAndCall.t.sol diff --git a/test/unit/local/shared/SharedPricingFlat.t.sol b/working/test/unit/local/shared/SharedPricingFlat.t.sol similarity index 100% rename from test/unit/local/shared/SharedPricingFlat.t.sol rename to working/test/unit/local/shared/SharedPricingFlat.t.sol diff --git a/test/unit/local/shared/SharedPricingHarberger.t.sol b/working/test/unit/local/shared/SharedPricingHarberger.t.sol similarity index 100% rename from test/unit/local/shared/SharedPricingHarberger.t.sol rename to working/test/unit/local/shared/SharedPricingHarberger.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_add.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_add.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_add.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_add.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_bidName.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_bidName.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_bidName.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_bidName.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_buyName.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_buyName.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_buyName.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_buyName.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_fundName.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_fundName.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_fundName.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_fundName.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_gasAirdrop.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_gasAirdrop.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_gasAirdrop.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_gasAirdrop.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_pokeName.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_pokeName.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_pokeName.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_pokeName.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_refund.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_refund.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_refund.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_refund.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_remove.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_remove.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_remove.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_remove.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_setDefaultClusterName.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_setDefaultClusterName.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_setDefaultClusterName.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_setDefaultClusterName.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_setWalletName.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_setWalletName.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_setWalletName.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_setWalletName.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_transferName.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_transferName.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_transferName.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_transferName.t.sol diff --git a/test/unit/remote/concrete/inbound/Endpoint_verify.t.sol b/working/test/unit/remote/concrete/inbound/Endpoint_verify.t.sol similarity index 100% rename from test/unit/remote/concrete/inbound/Endpoint_verify.t.sol rename to working/test/unit/remote/concrete/inbound/Endpoint_verify.t.sol