Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/ens/INameRegistrarController.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

struct Price {
uint256 base;
Expand Down
2 changes: 1 addition & 1 deletion contracts/l1/MintController.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

import {MintContext, MINT_CONTEXT} from "./Types.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
Expand Down
20 changes: 19 additions & 1 deletion contracts/l1/Types.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

bytes32 constant MINT_CONTEXT = keccak256(
"MintContext(string label,bytes32 parentNode,address owner,uint256 price,uint256 fee,address paymentReceiver,uint256 expiry,uint256 signatureExpiry,address verifiedMinter,uint32 fuses)"
Expand All @@ -16,4 +16,22 @@ struct MintContext {
uint256 signatureExpiry;
address verifiedMinter;
uint32 fuses;
}

bytes32 constant MINT_CONTEXT_V2 = keccak256(
"MintContextV2(string label,bytes32 parentNode,address owner,uint256 price,uint256 fee,address[] paymentReceivers,uint256[] paymentPercentages,uint256 expiry,uint256 signatureExpiry,address verifiedMinter,uint32 fuses)"
);

struct MintContextV2 {
address owner;
string label;
bytes32 parentNode;
uint256 price;
uint256 fee;
address[] paymentReceivers;
uint256[] paymentPercentages;
uint64 expiry;
uint256 signatureExpiry;
address verifiedMinter;
uint32 fuses;
}
262 changes: 262 additions & 0 deletions contracts/l1/v2/MintControllerV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {MintContextV2, MINT_CONTEXT_V2} from "../Types.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {Controllable} from "../../controllers/Controllable.sol";
import {IMulticallable} from "@ensdomains/ens-contracts/contracts/resolvers/Multicallable.sol";
import {INameWrapper, CANNOT_UNWRAP} from "../../ens/INameWrapper.sol";

contract MintControllerV2 is Controllable, EIP712, ERC1155Holder {
mapping(address => bool) private verifiers;
mapping(bytes32 => bool) private fuseBurned;
address private treasury;
address private wrapperProxy;
address private nameWrapper;
address private publicResolver;
uint64 private TTL = 0;

event SubnameMinted(
bytes32 parentNode,
string label,
uint256 price,
uint256 fee,
address[] paymentReceivers,
uint256[] paymentPercentages,
address owner,
bytes extraData
);

constructor(
address _verifier,
address _treasury,
address _wrapperProxy,
address _nameWrapper,
address _publicResolver
) EIP712("namespace", "2") {
verifiers[_verifier] = true;
treasury = _treasury;
wrapperProxy = _wrapperProxy;
nameWrapper = _nameWrapper;
publicResolver = _publicResolver;
}

function mint(
MintContextV2 calldata ctx,
bytes calldata sig,
bytes[] calldata resolverData,
bytes calldata extraData
) public payable {
verifySignature(ctx, sig);
ensureCannotUnwrapFuseBurned(ctx.parentNode);

if (resolverData.length > 0) {
mintWithData(ctx, resolverData);
} else {
mintSimple(ctx);
}

transferFunds(ctx.paymentReceivers, ctx.paymentPercentages, ctx.price, ctx.fee);

emit SubnameMinted(
ctx.parentNode,
ctx.label,
ctx.price,
ctx.fee,
ctx.paymentReceivers,
ctx.paymentPercentages,
ctx.owner,
extraData
);
}

function mintWithData(
MintContextV2 calldata context,
bytes[] calldata resolverData
) internal {
bytes32 subnameNode = INameWrapper(wrapperProxy).setSubnodeRecord(
context.parentNode,
context.label,
address(this),
publicResolver,
TTL,
context.fuses,
context.expiry
);

_setRecords(publicResolver, subnameNode, resolverData);

INameWrapper(nameWrapper).safeTransferFrom(
address(this),
context.owner,
uint256(subnameNode),
1,
bytes("")
);
}

function mintSimple(MintContextV2 calldata context) internal {
INameWrapper(wrapperProxy).setSubnodeRecord(
context.parentNode,
context.label,
context.owner,
publicResolver,
TTL,
context.fuses,
context.expiry
);
}

function verifySignature(
MintContextV2 calldata context,
bytes calldata signature
) internal view {
require(context.signatureExpiry > block.timestamp, "Signature expired");

require(context.verifiedMinter == msg.sender, "Not verified minter");

bytes32 signatureDigest = _createSignatureDigest(context);
address extractedSigner = ECDSA.recover(signatureDigest, signature);

require(verifiers[extractedSigner], "Invalid signature");
}

function transferFunds(
address[] calldata paymentReceivers,
uint256[] calldata paymentPercentages,
uint256 price,
uint256 fees
) internal {
uint256 totalPrice = price + fees;
uint256 ethAmount = msg.value;
require(ethAmount >= totalPrice, "Insufficient balance");

// Validate payment receivers and percentages
require(
paymentReceivers.length == paymentPercentages.length,
"Payment receivers and percentages length mismatch"
);
require(paymentReceivers.length > 0, "No payment receivers");

// Validate that percentages sum to 10000 (100.00% in basis points)
uint256 totalPercentage = 0;
for (uint256 i = 0; i < paymentPercentages.length; i++) {
totalPercentage += paymentPercentages[i];
}
require(totalPercentage == 10000, "Payment percentages must sum to 10000");

// Split and transfer price to payment receivers
if (price > 0) {
uint256 totalDistributed = 0;
for (uint256 i = 0; i < paymentReceivers.length; i++) {
uint256 amount = (price * paymentPercentages[i]) / 10000;
totalDistributed += amount;

(bool sent, ) = payable(paymentReceivers[i]).call{value: amount}("");
require(sent, "Could not transfer ETH to payment receiver");
}

// Handle any rounding remainder
uint256 remainder = price - totalDistributed;
if (remainder > 0) {
(bool sent, ) = payable(paymentReceivers[0]).call{value: remainder}("");
require(sent, "Could not transfer remainder to payment receiver");
}
}

// Transfer fees to treasury
if (fees > 0) {
(bool sentToTreasury, ) = payable(treasury).call{value: fees}("");
require(sentToTreasury, "Could not transfer ETH to treasury");
}

// Return remainder to sender
uint256 remainder = ethAmount - totalPrice;
if (remainder > 0) {
(bool remainderSent, ) = payable(msg.sender).call{value: remainder}("");
require(remainderSent, "Could not transfer ETH to msg.sender");
}
}

function _hashAddressArray(address[] calldata arr) internal pure returns (bytes32) {
bytes32[] memory hashes = new bytes32[](arr.length);
for (uint256 i = 0; i < arr.length; i++) {
hashes[i] = keccak256(abi.encode(arr[i]));
}
return keccak256(abi.encodePacked(hashes));
}

function _hashUint256Array(uint256[] calldata arr) internal pure returns (bytes32) {
bytes32[] memory hashes = new bytes32[](arr.length);
for (uint256 i = 0; i < arr.length; i++) {
hashes[i] = keccak256(abi.encode(arr[i]));
}
return keccak256(abi.encodePacked(hashes));
}

function _createSignatureDigest(
MintContextV2 calldata context
) internal view returns (bytes32) {
return
_hashTypedDataV4(
keccak256(
abi.encode(
MINT_CONTEXT_V2,
keccak256(abi.encodePacked(context.label)),
context.parentNode,
context.owner,
context.price,
context.fee,
_hashAddressArray(context.paymentReceivers),
_hashUint256Array(context.paymentPercentages),
context.expiry,
context.signatureExpiry,
context.verifiedMinter,
context.fuses
)
)
);
}

function ensureCannotUnwrapFuseBurned(bytes32 name) internal {
if (fuseBurned[name]) {
return;
}

if (!INameWrapper(nameWrapper).allFusesBurned(name, CANNOT_UNWRAP)) {
INameWrapper(wrapperProxy).setFuses(name, uint16(CANNOT_UNWRAP));
}

fuseBurned[name] = true;
}

function _setRecords(
address resolverAddress,
bytes32 subnameNode,
bytes[] calldata data
) internal {
IMulticallable(resolverAddress).multicallWithNodeCheck(
subnameNode,
data
);
}

function setVerifier(address verifier) external onlyOwner {
verifiers[verifier] = true;
}

function removeVerifier(address verifier) external onlyOwner {
verifiers[verifier] = false;
}

function setDefaultResolver(address resolver) external onlyOwner {
publicResolver = resolver;
}

function setTreasury(address _treasury) external onlyOwner {
treasury = _treasury;
}
}

33 changes: 32 additions & 1 deletion contracts/l2/Types.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

bytes32 constant MINT_CONTEXT = keccak256(
"MintContext(string label,bytes32 parentNode,address owner,uint256 price,uint256 fee,address paymentReceiver,uint256 expiry,uint256 signatureExpiry,address verifiedMinter)"
Expand Down Expand Up @@ -45,6 +45,37 @@ bytes32 constant EXTEND_EXPIRY_CONTEXT = keccak256(
"ExtendExpiryContext(bytes32 node,uint256 expiry,uint256 price,uint256 fee,address paymentReceiver,uint256 signatureExpiry)"
);

bytes32 constant MINT_CONTEXT_V2 = keccak256(
"MintContextV2(string label,bytes32 parentNode,address owner,uint256 price,uint256 fee,address[] paymentReceivers,uint256[] paymentPercentages,uint256 expiry,uint256 signatureExpiry,address verifiedMinter)"
);

struct MintContextV2 {
address owner;
string label;
bytes32 parentNode;
uint256 price;
uint256 fee;
address[] paymentReceivers;
uint256[] paymentPercentages;
uint256 expiry;
uint256 signatureExpiry;
address verifiedMinter;
}

bytes32 constant EXTEND_EXPIRY_CONTEXT_V2 = keccak256(
"ExtendExpiryContextV2(bytes32 node,uint256 expiry,uint256 price,uint256 fee,address[] paymentReceivers,uint256[] paymentPercentages,uint256 signatureExpiry)"
);

struct ExtendExpiryContextV2 {
bytes32 node;
uint256 expiry;
uint256 price;
uint256 fee;
address[] paymentReceivers;
uint256[] paymentPercentages;
uint256 signatureExpiry;
}

enum ExpirableType {
NonExpirable,
Expirable
Expand Down
2 changes: 1 addition & 1 deletion contracts/l2/bulk-ens-registrar/BulkNameRegistrar.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../../ens/INameRegistrarController.sol";
Expand Down
2 changes: 1 addition & 1 deletion contracts/l2/bulk-ens-registrar/Types.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

struct RegistrationContext {
string name;
Expand Down
2 changes: 1 addition & 1 deletion contracts/l2/controller/ControllerBase.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SignatureVerifier} from "./SignatureVerifier.sol";
Expand Down
2 changes: 1 addition & 1 deletion contracts/l2/controller/NameRegistryController.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SignatureVerifier} from "./SignatureVerifier.sol";
Expand Down
2 changes: 1 addition & 1 deletion contracts/l2/controller/RegistryControllerProxy.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

import {Controllable} from "../../controllers/Controllable.sol";
import {IEnsNameRegistry} from "../registries/IEnsNameRegistry.sol";
Expand Down
2 changes: 1 addition & 1 deletion contracts/l2/controller/RegistryExpiryExtender.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

import "../Types.sol";
import {INodeRegistryResolver} from "../registry-resolver/INodeRegistryResolver.sol";
Expand Down
Loading