// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.9;
/// @title Tangle, a token implementation using EIP-2535: Multi-Facet Proxy
/// @author Brad Brown
/// @notice Pieces of this contract can be updated without needing to redeploy
/// the entire contract
/// @dev implements IDiamondCut and IDiamondLoupe
contract Tangle {
mapping(bytes4 => address) private selectorToAddress;
/// @notice The owner of this contract
address public owner;
enum FacetCutAction {Add, Replace, Remove}
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
struct Facet {
address facetAddress;
bytes4[] functionSelectors;
}
address[] private addresses;
mapping(address => uint) private addressIndex;
mapping(address => bytes4[]) private addressToSelectors;
mapping(bytes4 => uint) private selectorIndex;
/// @notice Records all functions added, replaced, or removed from this
/// contract
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
/// @notice set owner to deployer
constructor() {
owner = msg.sender;
}
/// @notice payable fallback, does nothing
receive() external payable {}
/// @notice executes calldata via delegatecall to address if
/// calldata's selector is assigned
/// @dev Input is calldata
/// @return bytes response from delegatecall
fallback (bytes calldata) external payable returns (bytes memory) {
address address_ = selectorToAddress[msg.sig];
require(address_ != address(0), "zero facet");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(
gas(),
address_,
0,
calldatasize(),
0,
0
)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return (0, returndatasize()) }
}
}
/// @notice checks if an address is in use as a facet
/// @param address_ an address to a facet
/// @return bool whether the address is in use or not
function facetAddressExists(address address_)
internal
view
returns
(bool)
{
if (addresses.length == 0)
return false;
if (addresses[0] != address_ && addressIndex[address_] == 0)
return false;
return true;
}
/// @notice assigns a selector to an address, revert if selector already
/// assigned
/// @param selector an 8 byte function selector
/// @param facetAddress_ an address to a facet
function addSelector(
bytes4 selector,
address facetAddress_
) internal {
address currentFacetAddress = selectorToAddress[selector];
require(currentFacetAddress == address(0), "selector add");
selectorToAddress[selector] = facetAddress_;
selectorIndex[selector] = addressToSelectors[facetAddress_].length;
addressToSelectors[facetAddress_].push(selector);
}
/// @notice removes a selector from an address, revert if selector isn't
/// assigned
/// @param selector an 8 byte function selector
/// @param facetAddress_ an address to a facet
function removeSelector(
bytes4 selector,
address facetAddress_
) internal {
address currentFacetAddress = selectorToAddress[selector];
require(currentFacetAddress != address(0), "selector remove");
bytes4[] memory selectors = addressToSelectors[facetAddress_];
bytes4 lastSelector = selectors[selectors.length - 1];
if (lastSelector != selector) {
selectorIndex[lastSelector] = selectorIndex[selector];
selectors[selectorIndex[selector]] = lastSelector;
}
if (selectors.length > 0) {
assembly {
mstore(selectors, sub(mload(selectors), 1))
}
addressToSelectors[facetAddress_] = selectors;
}
if (selectors.length == 0) {
address lastAddress = addresses[addresses.length - 1];
if (lastAddress != facetAddress_) {
addressIndex[lastAddress] = addressIndex[facetAddress_];
addresses[addressIndex[facetAddress_]] = lastAddress;
}
addresses.pop();
addressIndex[facetAddress_] = 0;
}
selectorToAddress[selector] = address(0);
}
/// @notice reassigns a selector to an address, revert if no change in
/// selector address
/// @param selector an 8 byte function selector
/// @param facetAddress_ an address to a facet
function replaceSelector(
bytes4 selector,
address facetAddress_
) internal {
address currentFacetAddress = selectorToAddress[selector];
require(currentFacetAddress != facetAddress_, "selector replace");
bytes4[] memory selectors = addressToSelectors[currentFacetAddress];
bytes4 lastSelector = selectors[selectors.length - 1];
if (lastSelector != selector) {
selectorIndex[lastSelector] = selectorIndex[selector];
selectors[selectorIndex[selector]] = lastSelector;
}
if (selectors.length > 0) {
assembly {
mstore(selectors, sub(mload(selectors), 1))
}
addressToSelectors[currentFacetAddress] = selectors;
}
if (selectors.length == 0) {
address lastAddress = addresses[addresses.length - 1];
if (lastAddress != currentFacetAddress) {
addressIndex[lastAddress] = addressIndex[currentFacetAddress];
addresses[addressIndex[currentFacetAddress]] = lastAddress;
}
addresses.pop();
addressIndex[currentFacetAddress] = 0;
}
selectorToAddress[selector] = facetAddress_;
selectorIndex[selector] = addressToSelectors[facetAddress_].length;
addressToSelectors[facetAddress_].push(selector);
}
/// @notice Add/replace/remove any number of functions and optionally
/// execute a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and
/// arguments _calldata is executed with delegatecall on _init
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external {
require(msg.sender == owner, "not owner");
bool changesMade = false;
for (uint i = 0; i < _diamondCut.length; i++) {
FacetCut memory facetCut = _diamondCut[i];
address facetAddress_ = _diamondCut[i].facetAddress;
if (!facetAddressExists(facetAddress_)) {
addressIndex[facetAddress_] = addresses.length;
addresses.push(facetCut.facetAddress);
}
for (uint j = 0; j < facetCut.functionSelectors.length; j++) {
bytes4 selector = facetCut.functionSelectors[j];
if (facetCut.action == FacetCutAction.Add) {
addSelector(selector, facetAddress_);
if (!changesMade) changesMade = true;
}
if (facetCut.action == FacetCutAction.Replace) {
replaceSelector(selector, facetAddress_);
if (!changesMade) changesMade = true;
}
if (facetCut.action == FacetCutAction.Remove) {
removeSelector(selector, facetAddress_);
if (!changesMade) changesMade = true;
}
}
}
if (_init != address(0)) {
require(_calldata.length > 0, "empty calldata");
(bool success,) = _init.delegatecall(_calldata);
require(success, "call unsuccessful");
}
if (changesMade) emit DiamondCut(_diamondCut, _init, _calldata);
}
/// @notice Gets all facet addresses and their four byte function
/// selectors.
/// @return facets_ Facet
function facets() external view returns (Facet[] memory) {
Facet[] memory facets_ = new Facet[](addresses.length);
for (uint i = 0; i < addresses.length; i++) {
Facet memory facet;
facet.facetAddress = addresses[i];
facet.functionSelectors = addressToSelectors[addresses[i]];
facets_[i] = facet;
}
return facets_;
}
/// @notice Gets all the function selectors supported by a specific facet.
/// @param _facet The facet address.
/// @return facetFunctionSelectors_
function facetFunctionSelectors(
address _facet
)
external
view
returns
(bytes4[] memory)
{
return addressToSelectors[_facet];
}
/// @notice Get all the facet addresses used by a diamond.
/// @return facetAddresses_
function facetAddresses()
external
view
returns
(address[] memory)
{
return addresses;
}
/// @notice Gets the facet that supports the given selector.
/// @dev If facet is not found return address(0).
/// @param _functionSelector The function selector.
/// @return facetAddress_ The facet address.
function facetAddress(bytes4 _functionSelector)
external
view
returns
(address)
{
return selectorToAddress[_functionSelector];
}
}
{
"compilationTarget": {
"tangle.sol": "Tangle"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 10
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"components":[{"internalType":"address","name":"facetAddress","type":"address"},{"internalType":"enum Tangle.FacetCutAction","name":"action","type":"uint8"},{"internalType":"bytes4[]","name":"functionSelectors","type":"bytes4[]"}],"indexed":false,"internalType":"struct Tangle.FacetCut[]","name":"_diamondCut","type":"tuple[]"},{"indexed":false,"internalType":"address","name":"_init","type":"address"},{"indexed":false,"internalType":"bytes","name":"_calldata","type":"bytes"}],"name":"DiamondCut","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"components":[{"internalType":"address","name":"facetAddress","type":"address"},{"internalType":"enum Tangle.FacetCutAction","name":"action","type":"uint8"},{"internalType":"bytes4[]","name":"functionSelectors","type":"bytes4[]"}],"internalType":"struct Tangle.FacetCut[]","name":"_diamondCut","type":"tuple[]"},{"internalType":"address","name":"_init","type":"address"},{"internalType":"bytes","name":"_calldata","type":"bytes"}],"name":"diamondCut","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_functionSelector","type":"bytes4"}],"name":"facetAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"facetAddresses","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_facet","type":"address"}],"name":"facetFunctionSelectors","outputs":[{"internalType":"bytes4[]","name":"","type":"bytes4[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"facets","outputs":[{"components":[{"internalType":"address","name":"facetAddress","type":"address"},{"internalType":"bytes4[]","name":"functionSelectors","type":"bytes4[]"}],"internalType":"struct Tangle.Facet[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]