// SPDX-License-Identifier: MIT/**
* Author: Lambdalf the White
*/pragmasolidity 0.8.17;import"../interfaces/IContractState.sol";
abstractcontractContractStateisIContractState{
// Enum to represent the sale state, defaults to ``PAUSED``.uint8publicconstant PAUSED =0;
// The current state of the contractuint8private _contractState;
// **************************************// ***** MODIFIER *****// **************************************/**
* @dev Ensures that contract state is `expectedState_`.
*
* @param expectedState_ : the desirable contract state
*/modifierisState(uint8 expectedState_) {
if (_contractState != expectedState_) {
revert ContractState_INCORRECT_STATE(_contractState);
}
_;
}
/**
* @dev Ensures that contract state is not `unexpectedState_`.
*
* @param unexpectedState_ : the undesirable contract state
*/modifierisNotState(uint8 unexpectedState_) {
if (_contractState == unexpectedState_) {
revert ContractState_INCORRECT_STATE(_contractState);
}
_;
}
// **************************************// **************************************// ***** INTERNAL *****// **************************************/**
* @dev Internal function setting the contract state to `newState_`.
*
* Note: Contract state defaults to ``PAUSED``.
* To maintain extendability, this value kept as uint8 instead of enum.
* As a result, it is possible to set the state to an incorrect value.
* To avoid issues, `newState_` should be validated before calling this function
*/function_setContractState(uint8 newState_) internalvirtual{
uint8 _previousState_ = _contractState;
_contractState = newState_;
emit ContractStateChanged(_previousState_, newState_);
}
// **************************************// **************************************// ***** VIEW *****// **************************************/**
* @dev Returns the current contract state.
*
* @return uint8 : the current contract state
*/functiongetContractState() publicvirtualviewoverridereturns (uint8) {
return _contractState;
}
// **************************************
}
Contract Source Code
File 2 of 13: ERC173.sol
// SPDX-License-Identifier: MIT/**
* Author: Lambdalf the White
*/pragmasolidity 0.8.17;import"../interfaces/IERC173.sol";
import"../interfaces/IERC173Errors.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/abstractcontractERC173isIERC173, IERC173Errors{
// The owner of the contractaddressprivate _owner;
// **************************************// ***** MODIFIER *****// **************************************/**
* @dev Throws if called by any account other than the owner.
*/modifieronlyOwner() {
if (owner() !=msg.sender) {
revert IERC173_NOT_OWNER(msg.sender);
}
_;
}
// **************************************// **************************************// ***** INTERNAL *****// **************************************/**
* @dev Sets the contract owner.
*
* Note: This function needs to be called in the contract constructor to initialize the contract owner,
* if it is not, then parts of the contract might be non functional
*
* @param owner_ : address that owns the contract
*/function_setOwner(address owner_) internal{
_owner = owner_;
}
// **************************************// **************************************// ***** CONTRACT OWNER *****// **************************************/**
* @dev Transfers ownership of the contract to `newOwner_`.
*
* @param newOwner_ : address of the new contract owner
*
* Requirements:
*
* - Caller must be the contract owner.
*/functiontransferOwnership(address newOwner_) publicvirtualonlyOwner{
address _oldOwner_ = _owner;
_owner = newOwner_;
emit OwnershipTransferred(_oldOwner_, newOwner_);
}
// **************************************// **************************************// ***** VIEW *****// **************************************/**
* @dev Returns the address of the current contract owner.
*
* @return address : the current contract owner
*/functionowner() publicviewvirtualreturns (address) {
return _owner;
}
// **************************************
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import {RLPReader} from"../lib/RLPReader.sol";
import {MerklePatriciaProof} from"../lib/MerklePatriciaProof.sol";
import {Merkle} from"../lib/Merkle.sol";
import"../lib/ExitPayloadReader.sol";
interfaceIFxStateSender{
functionsendMessageToChild(address _receiver, bytescalldata _data) external;
}
contractICheckpointManager{
structHeaderBlock {
bytes32 root;
uint256 start;
uint256 end;
uint256 createdAt;
address proposer;
}
/**
* @notice mapping of checkpoint header numbers to block details
* @dev These checkpoints are submited by plasma contracts
*/mapping(uint256=> HeaderBlock) public headerBlocks;
}
abstractcontractFxBaseRootTunnel{
usingRLPReaderforRLPReader.RLPItem;
usingMerkleforbytes32;
usingExitPayloadReaderforbytes;
usingExitPayloadReaderforExitPayloadReader.ExitPayload;
usingExitPayloadReaderforExitPayloadReader.Log;
usingExitPayloadReaderforExitPayloadReader.LogTopics;
usingExitPayloadReaderforExitPayloadReader.Receipt;
// keccak256(MessageSent(bytes))bytes32publicconstant SEND_MESSAGE_EVENT_SIG =0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036;
// state sender contract
IFxStateSender public fxRoot;
// root chain manager
ICheckpointManager public checkpointManager;
// child tunnel contract which receives and sends messages addresspublic fxChildTunnel;
// storage to avoid duplicate exitsmapping(bytes32=>bool) public processedExits;
constructor(address _checkpointManager, address _fxRoot) {
checkpointManager = ICheckpointManager(_checkpointManager);
fxRoot = IFxStateSender(_fxRoot);
}
// set fxChildTunnel if not set alreadyfunctionsetFxChildTunnel(address _fxChildTunnel) public{
require(fxChildTunnel ==address(0x0), "FxBaseRootTunnel: CHILD_TUNNEL_ALREADY_SET");
fxChildTunnel = _fxChildTunnel;
}
/**
* @notice Send bytes message to Child Tunnel
* @param message bytes message that will be sent to Child Tunnel
* some message examples -
* abi.encode(tokenId);
* abi.encode(tokenId, tokenMetadata);
* abi.encode(messageType, messageData);
*/function_sendMessageToChild(bytesmemory message) internal{
fxRoot.sendMessageToChild(fxChildTunnel, message);
}
function_validateAndExtractMessage(bytesmemory inputData) internalreturns (bytesmemory) {
ExitPayloadReader.ExitPayload memory payload = inputData.toExitPayload();
bytesmemory branchMaskBytes = payload.getBranchMaskAsBytes();
uint256 blockNumber = payload.getBlockNumber();
// checking if exit has already been processed// unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex)bytes32 exitHash =keccak256(
abi.encodePacked(
blockNumber,
// first 2 nibbles are dropped while generating nibble array// this allows branch masks that are valid but bypass exitHash check (changing first 2 nibbles only)// so converting to nibble array and then hashing it
MerklePatriciaProof._getNibbleArray(branchMaskBytes),
payload.getReceiptLogIndex()
)
);
require(
processedExits[exitHash] ==false,
"FxRootTunnel: EXIT_ALREADY_PROCESSED"
);
processedExits[exitHash] =true;
ExitPayloadReader.Receipt memory receipt = payload.getReceipt();
ExitPayloadReader.Log memory log = receipt.getLog();
// check child tunnelrequire(fxChildTunnel == log.getEmitter(), "FxRootTunnel: INVALID_FX_CHILD_TUNNEL");
bytes32 receiptRoot = payload.getReceiptRoot();
// verify receipt inclusionrequire(
MerklePatriciaProof.verify(
receipt.toBytes(),
branchMaskBytes,
payload.getReceiptProof(),
receiptRoot
),
"FxRootTunnel: INVALID_RECEIPT_PROOF"
);
// verify checkpoint inclusion
_checkBlockMembershipInCheckpoint(
blockNumber,
payload.getBlockTime(),
payload.getTxRoot(),
receiptRoot,
payload.getHeaderNumber(),
payload.getBlockProof()
);
ExitPayloadReader.LogTopics memory topics = log.getTopics();
require(
bytes32(topics.getField(0).toUint()) == SEND_MESSAGE_EVENT_SIG, // topic0 is event sig"FxRootTunnel: INVALID_SIGNATURE"
);
// received message data
(bytesmemory message) =abi.decode(log.getData(), (bytes)); // event decodes params again, so decoding bytes to get messagereturn message;
}
function_checkBlockMembershipInCheckpoint(uint256 blockNumber,
uint256 blockTime,
bytes32 txRoot,
bytes32 receiptRoot,
uint256 headerNumber,
bytesmemory blockProof
) privateviewreturns (uint256) {
(
bytes32 headerRoot,
uint256 startBlock,
,
uint256 createdAt,
) = checkpointManager.headerBlocks(headerNumber);
require(
keccak256(
abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot)
)
.checkMembership(
blockNumber-startBlock,
headerRoot,
blockProof
),
"FxRootTunnel: INVALID_HEADER"
);
return createdAt;
}
/**
* @notice receive message from L2 to L1, validated by proof
* @dev This function verifies if the transaction actually happened on child chain
*
* @param inputData RLP encoded data of the reference tx containing following list of fields
* 0 - headerNumber - Checkpoint header block number containing the reference tx
* 1 - blockProof - Proof that the block header (in the child chain) is a leaf in the submitted merkle root
* 2 - blockNumber - Block number containing the reference tx on child chain
* 3 - blockTime - Reference tx block time
* 4 - txRoot - Transactions root of block
* 5 - receiptRoot - Receipts root of block
* 6 - receipt - Receipt of the reference transaction
* 7 - receiptProof - Merkle proof of the reference receipt
* 8 - branchMask - 32 bits denoting the path of receipt in merkle tree
* 9 - receiptLogIndex - Log Index to read from the receipt
*/functionreceiveMessage(bytesmemory inputData) publicvirtual{
bytesmemory message = _validateAndExtractMessage(inputData);
_processMessageFromChild(message);
}
/**
* @notice Process message received from Child Tunnel
* @dev function needs to be implemented to handle message as per requirement
* This is called by onStateReceive function.
* Since it is called via a system call, any event will not be emitted during its execution.
* @param message bytes message that was sent from Child Tunnel
*/function_processMessageFromChild(bytesmemory message) virtualinternal;
}
Contract Source Code
File 5 of 13: IArrayErrors.sol
// SPDX-License-Identifier: MIT/**
* Author: Lambdalf the White
*/pragmasolidity 0.8.17;interfaceIArrayErrors{
/**
* @dev Thrown when two related arrays have different lengths
*/errorARRAY_LENGTH_MISMATCH();
}
Contract Source Code
File 6 of 13: IContractState.sol
// SPDX-License-Identifier: MIT/**
* Author: Lambdalf the White
*/pragmasolidity 0.8.17;interfaceIContractState{
/**
* @dev Thrown when a function is called with the wrong contract state.
*
* @param currentState the current state of the contract
*/errorContractState_INCORRECT_STATE(uint8 currentState);
/**
* @dev Thrown when trying to set the contract state to an invalid value.
*
* @param invalidState the invalid contract state
*/errorContractState_INVALID_STATE(uint8 invalidState);
/**
* @dev Emitted when the sale state changes
*
* @param previousState the previous state of the contract
* @param newState the new state of the contract
*/eventContractStateChanged(uint8indexed previousState, uint8indexed newState);
/**
* @dev Returns the current contract state.
*/functiongetContractState() externalviewreturns (uint8);
}
Contract Source Code
File 7 of 13: IERC173.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;// import "./IERC165.sol";/**
* @dev Required interface of an ERC173 compliant contract, as defined in the
* https://eips.ethereum.org/EIPS/eip-173[EIP].
*/interfaceIERC173/* is IERC165 */{
/**
* @dev This emits when ownership of a contract changes.
*
* @param previousOwner the previous contract owner
* @param newOwner the new contract owner
*/eventOwnershipTransferred(addressindexed previousOwner, addressindexed newOwner);
/**
* @notice Set the address of the new owner of the contract.
* @dev Set newOwner_ to address(0) to renounce any ownership.
*/functiontransferOwnership(address newOwner_) external;
/**
* @notice Returns the address of the owner.
*/functionowner() externalviewreturns(address);
}
Contract Source Code
File 8 of 13: IERC173Errors.sol
// SPDX-License-Identifier: MIT/**
* Author: Lambdalf the White
*/pragmasolidity 0.8.17;interfaceIERC173Errors{
/**
* @dev Thrown when `operator` is not the contract owner.
*
* @param operator address trying to use a function reserved to contract owner without authorization
*/errorIERC173_NOT_OWNER(address operator);
}
Contract Source Code
File 9 of 13: IERC721.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;// import "./IERC165.sol";/**
* @title ERC-721 Non-Fungible Token Standard
* @dev See https://eips.ethereum.org/EIPS/eip-721
* Note: the ERC-165 identifier for this interface is 0x80ac58cd.
*/interfaceIERC721/* is IERC165 */{
/**
* @dev This emits when the approved address for an NFT is changed or reaffirmed.
* The zero address indicates there is no approved address.
* When a Transfer event emits, this also indicates that the approved address for that NFT (if any) is reset to none.
*
* @param owner address that owns the token
* @param approved address that is allowed to manage the token
* @param tokenId identifier of the token being approved
*/eventApproval(addressindexed owner, addressindexed approved, uint256indexed tokenId);
/**
* @dev This emits when an operator is enabled or disabled for an owner. The operator can manage all NFTs of the owner.
*
* @param owner address that owns the tokens
* @param operator address that is allowed or not to manage the tokens
* @param approved whether the operator is allowed or not
*/eventApprovalForAll(addressindexed owner, addressindexed operator, bool approved);
/**
* @dev This emits when ownership of any NFT changes by any mechanism.
* This event emits when NFTs are created (`from` == 0) and destroyed (`to` == 0).
* Exception: during contract creation, any number of NFTs may be created and assigned without emitting Transfer.
* At the time of any transfer, the approved address for that NFT (if any) is reset to none.
*
* @param from address the token is being transferred from
* @param to address the token is being transferred to
* @param tokenId identifier of the token being transferred
*/eventTransfer(addressindexedfrom, addressindexed to, uint256indexed tokenId);
/**
* @notice Change or reaffirm the approved address for an NFT
* @dev The zero address indicates there is no approved address.
* Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.
*/functionapprove(address approved_, uint256 tokenId_) external;
/**
* @notice Transfers the ownership of an NFT from one address to another address
* @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this NFT.
* Throws if `from_` is not the current owner.
* Throws if `to_` is the zero address.
* Throws if `tokenId_` is not a valid NFT.
* When transfer is complete, this function checks if `to_` is a smart contract (code size > 0).
* If so, it calls {onERC721Received} on `to_` and throws if the return value is not
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
*/functionsafeTransferFrom(address from_, address to_, uint256 tokenId_, bytescalldata data_) external;
/**
* @notice Transfers the ownership of an NFT from one address to another address
* @dev This works identically to the other function with an extra data parameter,
* except this function just sets data to "".
*/functionsafeTransferFrom(address from_, address to_, uint256 tokenId_) external;
/**
* @notice Enable or disable approval for a third party ("operator") to manage all of `msg.sender`'s assets.
* @dev Emits the ApprovalForAll event. The contract MUST allow multiple operators per owner.
*/functionsetApprovalForAll(address operator_, bool approved_) external;
/**
* @notice Transfer ownership of an NFT.
* The caller is responsible to confirm that `to_` is capable of receiving nfts or
* else they may be permanently lost
* @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this NFT.
* Throws if `from_` is not the current owner.
* Throws if `to_` is the zero address.
* Throws if `tokenId_` is not a valid NFT.
*/functiontransferFrom(address from_, address to_, uint256 tokenId_) external;
/**
* @notice Count all NFTs assigned to an owner
* @dev NFTs assigned to the zero address are considered invalid. Throws for queries about the zero address.
*/functionbalanceOf(address owner_) externalviewreturns (uint256);
/**
* @notice Get the approved address for a single NFT
* @dev Throws if `tokenId_` is not a valid NFT.
*/functiongetApproved(uint256 tokenId_) externalviewreturns (address);
/**
* @notice Query if an address is an authorized operator for another address
*/functionisApprovedForAll(address owner_, address operator_) externalviewreturns (bool);
/**
* @notice Find the owner of an NFT
* @dev NFTs assigned to zero address are considered invalid, and queries
* about them do throw.
*/functionownerOf(uint256 tokenId_) externalviewreturns (address);
}
Contract Source Code
File 10 of 13: Merkle.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;libraryMerkle{
functioncheckMembership(bytes32 leaf,
uint256 index,
bytes32 rootHash,
bytesmemory proof
) internalpurereturns (bool) {
require(proof.length%32==0, "Invalid proof length");
uint256 proofHeight = proof.length/32;
// Proof of size n means, height of the tree is n+1.// In a tree of height n+1, max #leafs possible is 2 ^ nrequire(index <2** proofHeight, "Leaf index is too big");
bytes32 proofElement;
bytes32 computedHash = leaf;
for (uint256 i =32; i <= proof.length; i +=32) {
assembly {
proofElement :=mload(add(proof, i))
}
if (index %2==0) {
computedHash =keccak256(
abi.encodePacked(computedHash, proofElement)
);
} else {
computedHash =keccak256(
abi.encodePacked(proofElement, computedHash)
);
}
index = index /2;
}
return computedHash == rootHash;
}
}
Contract Source Code
File 11 of 13: MerklePatriciaProof.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import {RLPReader} from"./RLPReader.sol";
libraryMerklePatriciaProof{
/*
* @dev Verifies a merkle patricia proof.
* @param value The terminating value in the trie.
* @param encodedPath The path in the trie leading to value.
* @param rlpParentNodes The rlp encoded stack of nodes.
* @param root The root hash of the trie.
* @return The boolean validity of the proof.
*/functionverify(bytesmemory value,
bytesmemory encodedPath,
bytesmemory rlpParentNodes,
bytes32 root
) internalpurereturns (bool) {
RLPReader.RLPItem memory item = RLPReader.toRlpItem(rlpParentNodes);
RLPReader.RLPItem[] memory parentNodes = RLPReader.toList(item);
bytesmemory currentNode;
RLPReader.RLPItem[] memory currentNodeList;
bytes32 nodeKey = root;
uint256 pathPtr =0;
bytesmemory path = _getNibbleArray(encodedPath);
if (path.length==0) {
returnfalse;
}
for (uint256 i =0; i < parentNodes.length; i++) {
if (pathPtr > path.length) {
returnfalse;
}
currentNode = RLPReader.toRlpBytes(parentNodes[i]);
if (nodeKey !=keccak256(currentNode)) {
returnfalse;
}
currentNodeList = RLPReader.toList(parentNodes[i]);
if (currentNodeList.length==17) {
if (pathPtr == path.length) {
if (
keccak256(RLPReader.toBytes(currentNodeList[16])) ==keccak256(value)
) {
returntrue;
} else {
returnfalse;
}
}
uint8 nextPathNibble =uint8(path[pathPtr]);
if (nextPathNibble >16) {
returnfalse;
}
nodeKey =bytes32(
RLPReader.toUintStrict(currentNodeList[nextPathNibble])
);
pathPtr +=1;
} elseif (currentNodeList.length==2) {
uint256 traversed = _nibblesToTraverse(
RLPReader.toBytes(currentNodeList[0]),
path,
pathPtr
);
if (pathPtr + traversed == path.length) {
//leaf nodeif (
keccak256(RLPReader.toBytes(currentNodeList[1])) ==keccak256(value)
) {
returntrue;
} else {
returnfalse;
}
}
//extension nodeif (traversed ==0) {
returnfalse;
}
pathPtr += traversed;
nodeKey =bytes32(RLPReader.toUintStrict(currentNodeList[1]));
} else {
returnfalse;
}
}
}
function_nibblesToTraverse(bytesmemory encodedPartialPath,
bytesmemory path,
uint256 pathPtr
) privatepurereturns (uint256) {
uint256 len =0;
// encodedPartialPath has elements that are each two hex characters (1 byte), but partialPath// and slicedPath have elements that are each one hex character (1 nibble)bytesmemory partialPath = _getNibbleArray(encodedPartialPath);
bytesmemory slicedPath =newbytes(partialPath.length);
// pathPtr counts nibbles in path// partialPath.length is a number of nibblesfor (uint256 i = pathPtr; i < pathPtr + partialPath.length; i++) {
bytes1 pathNibble = path[i];
slicedPath[i - pathPtr] = pathNibble;
}
if (keccak256(partialPath) ==keccak256(slicedPath)) {
len = partialPath.length;
} else {
len =0;
}
return len;
}
// bytes b must be hp encodedfunction_getNibbleArray(bytesmemory b)
internalpurereturns (bytesmemory)
{
bytesmemory nibbles ="";
if (b.length>0) {
uint8 offset;
uint8 hpNibble =uint8(_getNthNibbleOfBytes(0, b));
if (hpNibble ==1|| hpNibble ==3) {
nibbles =newbytes(b.length*2-1);
bytes1 oddNibble = _getNthNibbleOfBytes(1, b);
nibbles[0] = oddNibble;
offset =1;
} else {
nibbles =newbytes(b.length*2-2);
offset =0;
}
for (uint256 i = offset; i < nibbles.length; i++) {
nibbles[i] = _getNthNibbleOfBytes(i - offset +2, b);
}
}
return nibbles;
}
function_getNthNibbleOfBytes(uint256 n, bytesmemory str)
privatepurereturns (bytes1)
{
returnbytes1(
n %2==0 ? uint8(str[n /2]) /0x10 : uint8(str[n /2]) %0x10
);
}
}
Contract Source Code
File 12 of 13: NuCyberStaking.sol
// SPDX-License-Identifier: MIT/**
* Author: Lambdalf the White
*/pragmasolidity 0.8.17;import"@lambdalf-dev/ethereum-contracts/contracts/interfaces/IArrayErrors.sol";
import"@lambdalf-dev/ethereum-contracts/contracts/utils/ContractState.sol";
import"@lambdalf-dev/ethereum-contracts/contracts/utils/ERC173.sol";
import"@lambdalf-dev/ethereum-contracts/contracts/interfaces/IERC721.sol";
import { FxBaseRootTunnel } from"fx-portal/contracts/tunnel/FxBaseRootTunnel.sol";
contractNuCyberStakingisIArrayErrors, ContractState, ERC173, FxBaseRootTunnel{
// **************************************// ***** CUSTOM TYPES *****// **************************************structStakedToken {
uint64 tokenId;
address beneficiary;
}
// **************************************// **************************************// ***** ERRORS *****// **************************************/**
* @dev Thrown when user tries to unstake a token they don't own
*
* @param tokenId the token being unstaked
*/errorNCS_TOKEN_NOT_OWNED(uint256 tokenId);
/**
* @dev Thrown when trying to stake while rewards are not set
*/errorNCS_REWARDS_NOT_SET();
// **************************************// **************************************// ***** EVENTS *****// **************************************/**
* @dev Emitted when a user sets a beneficiary address
*
* @param tokenId the token being unstaked
* @param beneficiary the address benefitting from the token
*/eventBenefitStarted(uint256indexed tokenId, addressindexed beneficiary);
/**
* @dev Emitted when a user sets a beneficiary address
*
* @param tokenId the token being unstaked
* @param beneficiary the address benefitting from the token
*/eventBenefitEnded(uint256indexed tokenId, addressindexed beneficiary);
// **************************************// **************************************// ***** BYTECODE VARIABLES *****// **************************************uint8publicconstant ACTIVE =1;
// **************************************// **************************************// ***** STORAGE VARIABLES *****// **************************************
IERC721 public nuCyber;
// Wallet address mapped to list of token Idsmapping(address=> StakedToken[]) private _stakedTokens;
// Beneficiary wallet address mapped to list of token Idsmapping(address=>uint256[]) private _benefitTokens;
// **************************************constructor(address nucyberContractAddress_, address cpManager_, address fxRoot_)
FxBaseRootTunnel(cpManager_, fxRoot_) {
nuCyber = IERC721(nucyberContractAddress_);
_setOwner(msg.sender);
}
// **************************************// ***** INTERNAL *****// **************************************/**
* @dev Internal function returning the benefit balance of `account_`.
*
* @param account_ the beneficiary address
*/function_balanceOfBenefit(address account_) internalviewreturns (uint256) {
return _benefitTokens[account_].length;
}
/**
* @dev Internal function returning the staking balance of `account_`.
*
* @param account_ the beneficiary address
*/function_balanceOfStaked(address account_) internalviewreturns (uint256) {
return _stakedTokens[account_].length;
}
/**
* @dev Internal function that ends a benefit.
*
* @param beneficiary_ the beneficiary address
* @param tokenId_ the token being unstaked
*
* Requirements:
*
* - Emits a {BenefitEnded} event
*/function_endBenefit(address beneficiary_, uint256 tokenId_) internal{
uint256 _last_ = _benefitTokens[beneficiary_].length;
uint256 _count_ = _last_;
bool _deleted_;
while(_count_ >0) {
unchecked {
--_count_;
}
if (_benefitTokens[beneficiary_][_count_] == tokenId_) {
if (_count_ != _last_ -1) {
_benefitTokens[beneficiary_][_count_] = _benefitTokens[beneficiary_][_last_ -1];
}
_benefitTokens[beneficiary_].pop();
_deleted_ =true;
}
}
if(! _deleted_) {
revert NCS_TOKEN_NOT_OWNED(tokenId_);
}
emit BenefitEnded(tokenId_, beneficiary_);
}
/**
* @dev Internal function that returns a specific staked token and its index
*
* @param tokenOwner_ the token owner
* @param tokenId_ the token being unstaked
*
* Requirements:
*
* - `tokenOwner_` must own `tokenId_`
*/function_findToken(address tokenOwner_, uint256 tokenId_) internalviewreturns (StakedToken memory, uint256) {
uint256 _count_ = _stakedTokens[tokenOwner_].length;
while(_count_ >0) {
unchecked {
--_count_;
}
if (_stakedTokens[tokenOwner_][_count_].tokenId == tokenId_) {
return (_stakedTokens[tokenOwner_][_count_], _count_);
}
}
revert NCS_TOKEN_NOT_OWNED(tokenId_);
}
/**
* @dev Internal function to process a message sent by the child contract on Polygon
* Note: In our situation, we do not expect to receive any message from the child contract.
*
* @param message the message sent by the child contract
*/function_processMessageFromChild(bytesmemory message) internaloverride{
// We don't need a message from child
}
/**
* @dev Internal function to send a message to the child contract on Polygon
*
* @param sender_ the address staking or unstaking one or more token
* @param amount_ the number of token being staked or unstaked
* @param isStake_ whether the token are being staked or unstaked
*/function_sendMessage(address sender_, uint16 amount_, bool isStake_) internal{
if (amount_ >0) {
_sendMessageToChild(
abi.encode(sender_, uint8(1), amount_, isStake_)
);
}
}
/**
* @dev Internal function that stakes `tokenId_` for `tokenOwner_`.
*
* @param tokenOwner_ the token owner
* @param tokenId_ the token being staked
* @param beneficiary_ an address that will benefit from the token being staked
*
* Requirements:
*
* - `tokenOwner_` must own `tokenId_`
* - This contract must be allowed to transfer NuCyber tokens on behalf of `tokenOwner_`
* - Emits a {BenefitStarted} event if `beneficiary_` is not null
*/function_stakeToken(address tokenOwner_, uint256 tokenId_, address beneficiary_) internal{
_stakedTokens[tokenOwner_].push(StakedToken(uint64(tokenId_),beneficiary_));
if (beneficiary_ !=address(0)) {
_benefitTokens[beneficiary_].push(tokenId_);
emit BenefitStarted(tokenId_, beneficiary_);
}
try nuCyber.transferFrom(tokenOwner_, address(this), tokenId_) {}
catchError(stringmemory reason) {
revert(reason);
}
}
/**
* @dev Internal function that unstakes `tokenId_` for `tokenOwner_`.
*
* @param tokenOwner_ the token owner
* @param tokenId_ the token being unstaked
*
* Requirements:
*
* - `tokenOwner_` must own `tokenId_`
* - Emits a {BenefitEnded} event if `tokenId_` had a beneficiary
*/function_unstakeToken(address tokenOwner_, uint256 tokenId_) internal{
uint256 _last_ = _stakedTokens[tokenOwner_].length;
uint256 _count_ = _last_;
bool _deleted_;
while(_count_ >0) {
unchecked {
--_count_;
}
if (_stakedTokens[tokenOwner_][_count_].tokenId == tokenId_) {
address _beneficiary_ = _stakedTokens[tokenOwner_][_count_].beneficiary;
if(_beneficiary_ !=address(0)) {
_endBenefit(_beneficiary_, tokenId_);
}
if (_count_ != _last_ -1) {
_stakedTokens[tokenOwner_][_count_] = _stakedTokens[tokenOwner_][_last_ -1];
}
_stakedTokens[tokenOwner_].pop();
_deleted_ =true;
}
}
if(! _deleted_) {
revert NCS_TOKEN_NOT_OWNED(tokenId_);
}
try nuCyber.transferFrom(address(this), tokenOwner_, tokenId_) {}
catchError(stringmemory reason) {
revert(reason);
}
}
// **************************************// **************************************// ***** PUBLIC *****// **************************************/**
* @dev Stakes a batch of NuCyber at once.
*
* @param tokenIds_ the tokens being staked
* @param beneficiaries_ a list of addresses that will benefit from the tokens being staked
*
* Requirements:
*
* - Caller must own all of `tokenIds_`
* - Emits one or more {BenefitStarted} events if `beneficiaries_` is not null
* - This contract must be allowed to transfer NuCyber tokens on behalf of the caller
*/functionbulkStake(uint256[] memory tokenIds_, address[] memory beneficiaries_) publicisState(ACTIVE) {
if (fxChildTunnel ==address(0)) {
revert NCS_REWARDS_NOT_SET();
}
uint256 _len_ = tokenIds_.length;
if ( beneficiaries_.length!= _len_ ) {
revert ARRAY_LENGTH_MISMATCH();
}
while (_len_ >0) {
unchecked {
--_len_;
}
_stakeToken(msg.sender, tokenIds_[_len_], beneficiaries_[_len_]);
}
_sendMessage(msg.sender, uint16(tokenIds_.length), true);
}
/**
* @dev Unstakes a batch of NuCyber at once.
*
* @param tokenIds_ the tokens being unstaked
*
* Requirements:
*
* - Caller must own all of `tokenIds_`
* - Emits one or more {BenefitEnded} events if `tokenIds_` had beneficiaries
*/functionbulkUnstake(uint256[] memory tokenIds_) public{
uint256 _len_ = tokenIds_.length;
while (_len_ >0) {
unchecked {
--_len_;
}
_unstakeToken(msg.sender, tokenIds_[_len_]);
}
_sendMessage(msg.sender, uint16(tokenIds_.length), false);
}
/**
* @dev Stakes a NuCyber token.
*
* @param tokenId_ the token being staked
* @param beneficiary_ an address that will benefit from the token being staked
*
* Requirements:
*
* - Caller must own `tokenId_`
* - Emits a {BenefitStarted} event if `beneficiary_` is not null
* - This contract must be allowed to transfer NuCyber tokens on behalf of the caller
*/functionstake(uint256 tokenId_, address beneficiary_) publicisState(ACTIVE) {
if (fxChildTunnel ==address(0)) {
revert NCS_REWARDS_NOT_SET();
}
_stakeToken(msg.sender, tokenId_, beneficiary_);
_sendMessage(msg.sender, 1, true);
}
/**
* @dev Unstakes a NuCyber token.
*
* @param tokenId_ the token being unstaked
*
* Requirements:
*
* - Caller must own `tokenId_`
* - Emits a {BenefitEnded} event if `tokenId_` had a beneficiary
*/functionunstake(uint256 tokenId_) public{
_unstakeToken(msg.sender, tokenId_);
_sendMessage(msg.sender, 1, false);
}
/**
* @dev Updates the beneficiary of a staked token.
*
* @param tokenId_ the staked token
* @param newBeneficiary_ the address that will benefit from the staked token
*
* Requirements:
*
* - Caller must own `tokenId_`
* - Emits a {BenefitEnded} event if `tokenId_` had a beneficiary
* - Emits a {BenefitStarted} event if `newBeneficiary_` is not null
*/functionupdateBeneficiary(uint256 tokenId_, address newBeneficiary_) public{
(StakedToken memory _stakedToken_, uint256 _index_) = _findToken(msg.sender, tokenId_);
_stakedTokens[msg.sender][_index_].beneficiary = newBeneficiary_;
if (_stakedToken_.beneficiary !=address(0)) {
_endBenefit(_stakedToken_.beneficiary, tokenId_);
}
if (newBeneficiary_ !=address(0)) {
_benefitTokens[newBeneficiary_].push(tokenId_);
emit BenefitStarted(tokenId_, newBeneficiary_);
}
}
// **************************************// **************************************// ***** CONTRACT OWNER *****// **************************************/**
* @dev Sets the NuCyber contract address
*
* @param contractAddress_ the address of the NuCyber contract
*
* Requirements:
*
* - Caller must be the contract owner
*/functionsetNuCyberContract(address contractAddress_) externalonlyOwner{
nuCyber = IERC721(contractAddress_);
}
/**
* @dev Updates the contract state.
*
* @param newState_ the new sale state
*
* Requirements:
*
* - Caller must be the contract owner.
* - `newState_` must be a valid state.
*/functionsetContractState(uint8 newState_) externalonlyOwner{
if (newState_ > ACTIVE) {
revert ContractState_INVALID_STATE(newState_);
}
_setContractState(newState_);
}
/**
* @dev Updates the child contract on Polygon
*
* @param fxChildTunnel_ the new child contract on Polygon
*
* Requirements:
*
* - Caller must be the contract owner.
*/functionupdateFxChildTunnel(address fxChildTunnel_) externalonlyOwner{
fxChildTunnel = fxChildTunnel_;
}
// **************************************// **************************************// ***** VIEW *****// **************************************/**
* @dev Returns the number oof NuCyber staked and owned by `tokenOwner_`.
* Note: We need this function for collab.land to successfully give out token ownership roles
*
* @param tokenOwner_ address owning tokens
*/functionbalanceOf(address tokenOwner_) publicviewreturns (uint256) {
return nuCyber.balanceOf(tokenOwner_) + _balanceOfStaked(tokenOwner_) + _balanceOfBenefit(tokenOwner_);
}
/**
* @dev Returns the benefit balance of `account_`.
*
* @param account_ the address to check
*/functionbalanceOfBenefit(address account_) externalviewreturns (uint256) {
return _balanceOfBenefit(account_);
}
/**
* @dev Returns the staking balance of `account_`.
*
* @param account_ the address to check
*/functionbalanceOfStaked(address account_) externalviewreturns (uint256) {
return _balanceOfStaked(account_);
}
/**
* @dev Returns the list of tokens owned by `tokenOwner_`.
*
* @param tokenOwner_ address owning tokens
*/functionstakedTokens(address tokenOwner_) publicviewreturns (StakedToken[] memory) {
return _stakedTokens[tokenOwner_];
}
// **************************************
}
Contract Source Code
File 13 of 13: RLPReader.sol
/*
* @author Hamdi Allam hamdi.allam97@gmail.com
* Please reach out with any questions or concerns
*/pragmasolidity ^0.8.0;libraryRLPReader{
uint8constant STRING_SHORT_START =0x80;
uint8constant STRING_LONG_START =0xb8;
uint8constant LIST_SHORT_START =0xc0;
uint8constant LIST_LONG_START =0xf8;
uint8constant WORD_SIZE =32;
structRLPItem {
uint len;
uint memPtr;
}
structIterator {
RLPItem item; // Item that's being iterated over.uint nextPtr; // Position of the next item in the list.
}
/*
* @dev Returns the next element in the iteration. Reverts if it has not next element.
* @param self The iterator.
* @return The next element in the iteration.
*/functionnext(Iterator memoryself) internalpurereturns (RLPItem memory) {
require(hasNext(self));
uint ptr =self.nextPtr;
uint itemLength = _itemLength(ptr);
self.nextPtr = ptr + itemLength;
return RLPItem(itemLength, ptr);
}
/*
* @dev Returns true if the iteration has more elements.
* @param self The iterator.
* @return true if the iteration has more elements.
*/functionhasNext(Iterator memoryself) internalpurereturns (bool) {
RLPItem memory item =self.item;
returnself.nextPtr < item.memPtr + item.len;
}
/*
* @param item RLP encoded bytes
*/functiontoRlpItem(bytesmemory item) internalpurereturns (RLPItem memory) {
uint memPtr;
assembly {
memPtr :=add(item, 0x20)
}
return RLPItem(item.length, memPtr);
}
/*
* @dev Create an iterator. Reverts if item is not a list.
* @param self The RLP item.
* @return An 'Iterator' over the item.
*/functioniterator(RLPItem memoryself) internalpurereturns (Iterator memory) {
require(isList(self));
uint ptr =self.memPtr + _payloadOffset(self.memPtr);
return Iterator(self, ptr);
}
/*
* @param item RLP encoded bytes
*/functionrlpLen(RLPItem memory item) internalpurereturns (uint) {
return item.len;
}
/*
* @param item RLP encoded bytes
*/functionpayloadLen(RLPItem memory item) internalpurereturns (uint) {
return item.len - _payloadOffset(item.memPtr);
}
/*
* @param item RLP encoded list in bytes
*/functiontoList(RLPItem memory item) internalpurereturns (RLPItem[] memory) {
require(isList(item));
uint items = numItems(item);
RLPItem[] memory result =new RLPItem[](items);
uint memPtr = item.memPtr + _payloadOffset(item.memPtr);
uint dataLen;
for (uint i =0; i < items; i++) {
dataLen = _itemLength(memPtr);
result[i] = RLPItem(dataLen, memPtr);
memPtr = memPtr + dataLen;
}
return result;
}
// @return indicator whether encoded payload is a list. negate this function call for isData.functionisList(RLPItem memory item) internalpurereturns (bool) {
if (item.len ==0) returnfalse;
uint8 byte0;
uint memPtr = item.memPtr;
assembly {
byte0 :=byte(0, mload(memPtr))
}
if (byte0 < LIST_SHORT_START)
returnfalse;
returntrue;
}
/*
* @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory.
* @return keccak256 hash of RLP encoded bytes.
*/functionrlpBytesKeccak256(RLPItem memory item) internalpurereturns (bytes32) {
uint256 ptr = item.memPtr;
uint256 len = item.len;
bytes32 result;
assembly {
result :=keccak256(ptr, len)
}
return result;
}
functionpayloadLocation(RLPItem memory item) internalpurereturns (uint, uint) {
uint offset = _payloadOffset(item.memPtr);
uint memPtr = item.memPtr + offset;
uint len = item.len - offset; // data lengthreturn (memPtr, len);
}
/*
* @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory.
* @return keccak256 hash of the item payload.
*/functionpayloadKeccak256(RLPItem memory item) internalpurereturns (bytes32) {
(uint memPtr, uint len) = payloadLocation(item);
bytes32 result;
assembly {
result :=keccak256(memPtr, len)
}
return result;
}
/** RLPItem conversions into data types **/// @returns raw rlp encoding in bytesfunctiontoRlpBytes(RLPItem memory item) internalpurereturns (bytesmemory) {
bytesmemory result =newbytes(item.len);
if (result.length==0) return result;
uint ptr;
assembly {
ptr :=add(0x20, result)
}
copy(item.memPtr, ptr, item.len);
return result;
}
// any non-zero byte is considered truefunctiontoBoolean(RLPItem memory item) internalpurereturns (bool) {
require(item.len ==1);
uint result;
uint memPtr = item.memPtr;
assembly {
result :=byte(0, mload(memPtr))
}
return result ==0 ? false : true;
}
functiontoAddress(RLPItem memory item) internalpurereturns (address) {
// 1 byte for the length prefixrequire(item.len ==21);
returnaddress(uint160(toUint(item)));
}
functiontoUint(RLPItem memory item) internalpurereturns (uint) {
require(item.len >0&& item.len <=33);
uint offset = _payloadOffset(item.memPtr);
uint len = item.len - offset;
uint result;
uint memPtr = item.memPtr + offset;
assembly {
result :=mload(memPtr)
// shfit to the correct location if neccesaryiflt(len, 32) {
result :=div(result, exp(256, sub(32, len)))
}
}
return result;
}
// enforces 32 byte lengthfunctiontoUintStrict(RLPItem memory item) internalpurereturns (uint) {
// one byte prefixrequire(item.len ==33);
uint result;
uint memPtr = item.memPtr +1;
assembly {
result :=mload(memPtr)
}
return result;
}
functiontoBytes(RLPItem memory item) internalpurereturns (bytesmemory) {
require(item.len >0);
uint offset = _payloadOffset(item.memPtr);
uint len = item.len - offset; // data lengthbytesmemory result =newbytes(len);
uint destPtr;
assembly {
destPtr :=add(0x20, result)
}
copy(item.memPtr + offset, destPtr, len);
return result;
}
/*
* Private Helpers
*/// @return number of payload items inside an encoded list.functionnumItems(RLPItem memory item) privatepurereturns (uint) {
if (item.len ==0) return0;
uint count =0;
uint currPtr = item.memPtr + _payloadOffset(item.memPtr);
uint endPtr = item.memPtr + item.len;
while (currPtr < endPtr) {
currPtr = currPtr + _itemLength(currPtr); // skip over an item
count++;
}
return count;
}
// @return entire rlp item byte lengthfunction_itemLength(uint memPtr) privatepurereturns (uint) {
uint itemLen;
uint byte0;
assembly {
byte0 :=byte(0, mload(memPtr))
}
if (byte0 < STRING_SHORT_START)
itemLen =1;
elseif (byte0 < STRING_LONG_START)
itemLen = byte0 - STRING_SHORT_START +1;
elseif (byte0 < LIST_SHORT_START) {
assembly {
let byteLen :=sub(byte0, 0xb7) // # of bytes the actual length is
memPtr :=add(memPtr, 1) // skip over the first byte/* 32 byte word size */let dataLen :=div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len
itemLen :=add(dataLen, add(byteLen, 1))
}
}
elseif (byte0 < LIST_LONG_START) {
itemLen = byte0 - LIST_SHORT_START +1;
}
else {
assembly {
let byteLen :=sub(byte0, 0xf7)
memPtr :=add(memPtr, 1)
let dataLen :=div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
itemLen :=add(dataLen, add(byteLen, 1))
}
}
return itemLen;
}
// @return number of bytes until the datafunction_payloadOffset(uint memPtr) privatepurereturns (uint) {
uint byte0;
assembly {
byte0 :=byte(0, mload(memPtr))
}
if (byte0 < STRING_SHORT_START)
return0;
elseif (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START))
return1;
elseif (byte0 < LIST_SHORT_START) // being explicitreturn byte0 - (STRING_LONG_START -1) +1;
elsereturn byte0 - (LIST_LONG_START -1) +1;
}
/*
* @param src Pointer to source
* @param dest Pointer to destination
* @param len Amount of memory to copy from the source
*/functioncopy(uint src, uint dest, uint len) privatepure{
if (len ==0) return;
// copy as many word sizes as possiblefor (; len >= WORD_SIZE; len -= WORD_SIZE) {
assembly {
mstore(dest, mload(src))
}
src += WORD_SIZE;
dest += WORD_SIZE;
}
if (len ==0) return;
// left over bytes. Mask is used to remove unwanted bytes from the worduint mask =256** (WORD_SIZE - len) -1;
assembly {
let srcpart :=and(mload(src), not(mask)) // zero out srclet destpart :=and(mload(dest), mask) // retrieve the bytesmstore(dest, or(destpart, srcpart))
}
}
}