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
22 changes: 11 additions & 11 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ MNEMONIC_KEY="blossom spatial metal assault riot bullet truck update forward bra

DEPLOYER_PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE

# Alchemy key used to deploy smart contracts (includes archive nodes)
# Key should be the full URL
ALCHEMY_MAINNET_KEY=add-your-alchemy-key-here
ALCHEMY_RINKEBY_KEY=add-your-alchemy-key-here
ALCHEMY_ROPSTEN_KEY=add-your-alchemy-key-here
ALCHEMY_KOVAN_KEY=add-your-alchemy-key-here

# Matic key used to deploy smart contracts
# Key should be the full URL
MATIC_MAINNET_KEY=add-your-matic-vigil-key-here
MATIC_MUMBAI_KEY=add-your-matic-vigil-key-here
# Ethereum RPC URLs used to deploy smart contracts (archive nodes recommended)
# Must be the full URL, e.g. https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
RINKEBY_RPC_URL=https://eth-rinkeby.g.alchemy.com/v2/YOUR_API_KEY
ROPSTEN_RPC_URL=https://eth-ropsten.g.alchemy.com/v2/YOUR_API_KEY
KOVAN_RPC_URL=https://eth-kovan.g.alchemy.com/v2/YOUR_API_KEY

# Polygon RPC URLs used to deploy smart contracts
# Must be the full URL
MATIC_MAINNET_RPC_URL=https://polygon-mainnet.g.alchemy.com/v2/YOUR_API_KEY
MATIC_MUMBAI_RPC_URL=https://polygon-mumbai.g.alchemy.com/v2/YOUR_API_KEY

# Infura key used to deploy smart contracts
INFURA_KEY=add-your-infura-key-here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ abstract contract mod_initializer_Initializable_v1 is sto_Initializable {
!initializableStorage().initialized,
"Teller: already initialized"
);
initializableStorage().initialized = true;
_;
}
}
67 changes: 67 additions & 0 deletions contracts/nft/distributor/entry/roles.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { LibDiamond } from "../../../shared/libraries/LibDiamond.sol";
import { AccessControlEvents } from "../../../contexts/access-control/data.sol";
import "../../../contexts/access-control/storage/roles.sol";

contract ent_roles_NFTDistributor_v1 is sto_AccessControl_Roles {
/**
* @notice Checks if an account has a specific role.
* @param role Encoding of the role to check.
* @param account Address to check the {role} for.
*/
function hasRole(bytes32 role, address account)
external
view
returns (bool)
{
return accessControlRolesStore().roles[role].members[account];
}

/**
* @notice Grants an account a new role.
* @param role Encoding of the role to give.
* @param account Address to give the {role} to.
*
* Requirements:
* - Sender must be diamond owner.
*/
function grantRole(bytes32 role, address account) external {
LibDiamond.enforceIsContractOwner();
_grantRole(role, account);
}

/**
* @notice Removes a role from an account.
* @param role Encoding of the role to remove.
* @param account Address to remove the {role} from.
*
* Requirements:
* - Sender must be diamond owner.
*/
function revokeRole(bytes32 role, address account) external {
LibDiamond.enforceIsContractOwner();
_revokeRole(role, account);
}

/**
* @notice Removes a role from the sender.
* @param role Encoding of the role to remove.
*/
function renounceRole(bytes32 role) external {
_revokeRole(role, msg.sender);
}

function _grantRole(bytes32 role, address account) internal {
if (accessControlRolesStore().roles[role].members[account]) return;
accessControlRolesStore().roles[role].members[account] = true;
emit AccessControlEvents.RoleGranted(role, account, msg.sender);
}

function _revokeRole(bytes32 role, address account) internal {
if (!accessControlRolesStore().roles[role].members[account]) return;
accessControlRolesStore().roles[role].members[account] = false;
emit AccessControlEvents.RoleRevoked(role, account, msg.sender);
}
}
83 changes: 83 additions & 0 deletions contracts/nft/mainnet/MainnetTellerNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,89 @@ contract MainnetTellerNFT is IERC721ReceiverUpgradeable, TellerNFT_V2 {
return IERC721ReceiverUpgradeable.onERC721Received.selector;
}

/**
* @notice Admin function to mint a specific token ID to an address.
* @dev Unlike {mint}, this targets an exact tier token ID — used to re-issue
* the precise tokens lost in the bridge exploit rather than a tier-random one.
* @param to Address to mint to.
* @param id Token ID to mint.
* @param amount Amount to mint.
*/
function adminMint(
address to,
uint256 id,
uint256 amount
) external onlyRole(ADMIN) {
_mint(to, id, amount, "");
}

/**
* @notice Admin function to burn a token from an address.
* @param from Address to burn from.
* @param id Token ID to burn.
* @param amount Amount to burn.
*/
function adminBurn(
address from,
uint256 id,
uint256 amount
) external onlyRole(ADMIN) {
_burn(from, id, amount);
}

/**
* @notice Admin function to batch burn tokens from an address.
* @param from Address to burn from.
* @param ids Token IDs to burn.
* @param amounts Amounts to burn.
*/
function adminBurnBatch(
address from,
uint256[] calldata ids,
uint256[] calldata amounts
) external onlyRole(ADMIN) {
_burnBatch(from, ids, amounts);
}

/**
* @notice Admin function to forcibly transfer a token between addresses,
* bypassing owner approval. Used to return exploited NFTs to their rightful
* owners while preserving the original token (no burn/re-mint).
* @dev If `to` is a contract it must implement {IERC1155Receiver}, per the
* ERC1155 acceptance check.
* @param from Address to transfer from.
* @param to Address to transfer to.
* @param id Token ID to transfer.
* @param amount Amount to transfer.
*/
function adminForceTransfer(
address from,
address to,
uint256 id,
uint256 amount
) external onlyRole(ADMIN) {
_safeTransferFrom(from, to, id, amount, "");
}

/**
* @notice Admin function to forcibly batch-transfer tokens between
* addresses, bypassing owner approval.
* @dev If `to` is a contract it must implement {IERC1155Receiver}, per the
* ERC1155 acceptance check.
* @param from Address to transfer from.
* @param to Address to transfer to.
* @param ids Token IDs to transfer.
* @param amounts Amounts to transfer.
*/
function adminForceTransferBatch(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts
) external onlyRole(ADMIN) {
_safeBatchTransferFrom(from, to, ids, amounts, "");
}

/* Public Functions */

/**
Expand Down
93 changes: 93 additions & 0 deletions contracts/nft/polygon/PolyTellerNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ contract PolyTellerNFT is TellerNFT_V2 {

bytes32 public constant DEPOSITOR = keccak256("DEPOSITOR");

/**
* @notice The OpenZeppelin ProxyAdmin that owns this transparent proxy.
* @dev The only execution path that can reach {recoverAdmin} with
* `msg.sender == PROXY_ADMIN` is `ProxyAdmin.upgradeAndCall`, which
* delegatecalls the new implementation from the proxy in the same tx. A
* transparent proxy never routes ordinary fallback calls from its admin to
* the implementation, so after that single upgrade call {recoverAdmin} is
* unreachable — it is a one-shot recovery hook, not a standing backdoor.
*/
address private constant PROXY_ADMIN =
0x00BfeCF575FBDF4367dD70Dc9c729475173dBABf;

/**
* @notice It initializes the PolyTellerNFT adding a DEPOSITOR role for
* the ChildChainManager address.
Expand Down Expand Up @@ -45,6 +57,87 @@ contract PolyTellerNFT is TellerNFT_V2 {
_mintBatch(user, ids, amounts, data);
}

/**
* @notice Admin function to mint a token to an address.
* @param to Address to mint to.
* @param id Token ID to mint.
* @param amount Amount to mint.
*/
function adminMint(address to, uint256 id, uint256 amount) external onlyRole(ADMIN) {
_mint(to, id, amount, "");
}

/**
* @notice Admin function to burn a token from an address.
* @param from Address to burn from.
* @param id Token ID to burn.
* @param amount Amount to burn.
*/
function adminBurn(address from, uint256 id, uint256 amount) external onlyRole(ADMIN) {
_burn(from, id, amount);
}

/**
* @notice Admin function to batch burn tokens from an address.
* @param from Address to burn from.
* @param ids Token IDs to burn.
* @param amounts Amounts to burn.
*/
function adminBurnBatch(address from, uint256[] calldata ids, uint256[] calldata amounts) external onlyRole(ADMIN) {
_burnBatch(from, ids, amounts);
}

/**
* @notice Admin function to forcibly transfer a token between addresses,
* bypassing owner approval. Used to return exploited NFTs to their rightful
* owners while preserving the original token (no burn/re-mint).
* @dev If `to` is a contract it must implement {IERC1155Receiver}, per the
* ERC1155 acceptance check.
* @param from Address to transfer from.
* @param to Address to transfer to.
* @param id Token ID to transfer.
* @param amount Amount to transfer.
*/
function adminForceTransfer(address from, address to, uint256 id, uint256 amount) external onlyRole(ADMIN) {
_safeTransferFrom(from, to, id, amount, "");
}

/**
* @notice Admin function to forcibly batch-transfer tokens between
* addresses, bypassing owner approval.
* @dev If `to` is a contract it must implement {IERC1155Receiver}, per the
* ERC1155 acceptance check.
* @param from Address to transfer from.
* @param to Address to transfer to.
* @param ids Token IDs to transfer.
* @param amounts Amounts to transfer.
*/
function adminForceTransferBatch(address from, address to, uint256[] calldata ids, uint256[] calldata amounts) external onlyRole(ADMIN) {
_safeBatchTransferFrom(from, to, ids, amounts, "");
}

/**
* @notice One-shot ADMIN-role recovery, callable only by the ProxyAdmin via
* `upgradeAndCall`.
* @dev Grants the ADMIN role to `newAdmin` without requiring the caller to
* already hold ADMIN. This re-seats control when the role is held by an
* address we no longer wish to rely on: whoever controls the proxy upgrade
* authority outranks the role holder. Because a transparent proxy blocks the
* admin from reaching the implementation through the normal fallback, the
* only way to satisfy `msg.sender == PROXY_ADMIN` is the delegatecall that
* `ProxyAdmin.upgradeAndCall` performs during the upgrade itself — so this
* cannot be replayed afterward.
*
* After recovery, manage roles with the standard {grantRole} / {revokeRole}
* (ADMIN is its own role-admin), e.g. revoke the previous holder.
* @param newAdmin Address to grant the ADMIN role to.
*/
function recoverAdmin(address newAdmin) external {
require(msg.sender == PROXY_ADMIN, "PolyTellerNFT: only proxy admin");
require(newAdmin != address(0), "PolyTellerNFT: zero admin");
_setupRole(ADMIN, newAdmin);
}

/**
* @notice called when user wants to withdraw single token back to root chain
* @dev Should burn user's tokens. This transaction will be verified when exiting on root chain
Expand Down
Loading