PayrollManager

Git Source

Inherits: NoncesUpgradeable, ReentrancyGuardUpgradeable, Patchable, StateAware, AdminControlled, MezzEIP712, MezzERC721Upgradeable, IPayrollManager

Author: Daniel Yamagata

A soulbound ERC721 contract that manages the payroll of a company. The contract is controlled by the company's board of directors and a set of admins set by the board. Some actions require a 24-hour time delay before they are executable. This was done such that a compromised admin cannot drain a company's funds instantaneously. Rather, the action can be canceled by the board or another admin before its execution. These actions include:

  • Changing an employee's cash salary

  • Extending an employee's equity payments

  • Setting a new equity payment schedule for an employee

  • Paying cash directly to an employee

  • Paying equity directly to an employee

The tokens are never burned, even after an employee resigns or is terminated. This was done such to preserve the on-chain data that an employee once worked at a company. Whether or not an employee resigned or was terminated is tracked accordingly.

State Variables

HIRE_TYPEHASH

bytes32 public constant HIRE_TYPEHASH = keccak256(
    "Hire(address employee,uint256 annualCash,uint256 equityAmount,uint48 startDate,uint32 vestingDuration,uint32 vestingCliff,uint256 vestingInitialUnlock,uint256 employeeNonce)"
);

RESIGN_TYPEHASH

bytes32 public constant RESIGN_TYPEHASH = keccak256("Resign(uint256 tokenId,uint256 endDate,uint256 employeeNonce)");

EQUITY_EXTENSION_TYPEHASH

bytes32 public constant EQUITY_EXTENSION_TYPEHASH = keccak256(
    "EquityExtension(uint256 tokenId,uint256 equityAmount,uint256 vestingDurationIncrease,uint256 employeeNonce)"
);

PayrollManagerStorageLocation

bytes32 private constant PayrollManagerStorageLocation =
    0xf4d0e5fd25e5b6673737e0c7092828613247d816b3e8d4d3ee07c337421a2e00;

Functions

_getPayrollManagerStorage

function _getPayrollManagerStorage() internal pure returns (PayrollManagerStorage storage $);

requireEmployed

Reverts if the token ID does not correspond to a current employee

modifier requireEmployed(uint256 tokenId);

constructor

constructor(address _mezzHub, address _mezzMigrator) StateAware(_mezzHub) Patchable(_mezzMigrator);

init

Initializes the Payroll Manager. The 'initTreasury' is set as the controller of the contract. Can only be called once.

The payroll manager is initialized upon a company's deployment within the Mezz Deployer

function init(address initTreasury) external virtual initializer;

Parameters

NameTypeDescription

initTreasury

address

The treasury that will control the payroll manager

__PayrollManager_init

function __PayrollManager_init(address initTreasury) internal virtual onlyInitializing;

hire

Hires an employee with an annual salary and equity. An employee's start date must be at least 24 hours after calling this function. Only callable by 'admins' or the board of directors, who are the signers of the treasury

An employee signature must be included such that they accept their offer. This prevents companies from defrauding others by claiming that they have hired a certain employee. It also confirms that the employee has accepted the terms of the agreement, so that they do not have to be altered at a later time

function hire(DataTypes.HireData memory data, bytes memory employeeSignature)
    external
    virtual
    onlyAdmin
    nonReentrant
    pausable
    returns (uint256);

Parameters

NameTypeDescription

data

DataTypes.HireData

The data needed to hire an employee, defined as DataTypes.HireData

employeeSignature

bytes

The signature of the employee. The employee should sign the hash of 'getHireTransactionHash' with the terms of their agreement

Returns

NameTypeDescription

<none>

uint256

The ID of the payroll token

terminate

function terminate(uint256[] memory tokenIds) external virtual onlyAdmin freezable;

terminate

Terminates the employees associated with 'tokenId'. Collects all collectible cash for 'tokenId'. Only callable by an admin

function terminate(uint256 tokenId) public virtual onlyAdmin freezable;

increaseUnpaidTimeOff

function increaseUnpaidTimeOff(uint256[] memory tokenIds, uint48[] memory timeOff)
    external
    virtual
    onlyAdmin
    freezable;

increaseUnpaidTimeOff

Increases the unpaid time off for the employees associated with 'tokenId' by 'timeOff'. Only callable by an admin. Collects any collectible cash associated with 'tokenIds'

function increaseUnpaidTimeOff(uint256 tokenId, uint48 timeOff) external virtual onlyAdmin freezable;

collectCash

Collects the cash owed to an employee. If the treasury does not have a sufficcient cash balance, the cash owed to an employee is updated, such that it will be collectible at a later time. The cash is transferred to the employee's specified recipient, which may be different from the employee, themself.

This function is not access controlled. Any account can collect cash on behalf of an employee.

function collectCash(uint256 tokenId) public virtual pausable returns (uint256);

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

Returns

NameTypeDescription

<none>

uint256

The amount of cash paid, which may be different than the amount that was collectible. This is due to potential transfer fees that may be incurred by the cash asset.

collectEquity

Collects the equity owed to an employee. The common shares are transferred to the employee's specified recipient. Equity compensation will always be in the form of the company's common shares. This function is callable by any account

This function is not access controlled. Any account can collect cash on behalf of an employee. This function is will revert if the employee's vesting schedule has been completed or if they did not have a vesting schedule to begin with.

function collectEquity(uint256 tokenId) public virtual pausable returns (uint256);

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

Returns

NameTypeDescription

<none>

uint256

The amount of equity released

changeRecipient

Changes the recipient of the employee's salary to 'newRecipient'. Only callable by the owner of 'tokenId'

function changeRecipient(uint256 tokenId, address newRecipient) external virtual onlyTokenOwner(tokenId) pausable;

resign

Resigns the employee associated with 'tokenId'. Only callable by the token owner. The employee will continue to earn their salary until their 'salaryEndDate'

The 'salaryEndDate' cannot exceed 1 year into the future and cannot be in the past

function resign(uint256 tokenId, uint256 salaryEndDate) external onlyTokenOwner(tokenId) freezable;

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

salaryEndDate

uint256

The date that the employee will stop earning their salary

resignBySig

Resigns the employee associated with 'tokenId'. A signature from the employee is required. The employee will continue to earn their salary until their 'salaryEndDate'

function resignBySig(uint256 tokenId, uint256 salaryEndDate, bytes memory employeeSignature) external freezable;

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

salaryEndDate

uint256

The date that the employee will stop earning their salary

employeeSignature

bytes

The signature of the employee. The employee should sign the hash of 'getResignTransactionHash'

_resign

function _resign(uint256 tokenId, uint256 salaryEndDate) internal virtual;

proposeActions

Proposes a series of 'actions' to be executed in the future. Each action has a corresponding 'params' which are the abi-encoded arguments of the actions' corresponding function. Only callable by an admin.

function proposeActions(bytes4[] memory actions, bytes[] memory params)
    public
    virtual
    onlyAdmin
    pausable
    returns (bytes32[] memory);

Parameters

NameTypeDescription

actions

bytes4[]

The actions to be executed

params

bytes[]

The abi-encoded arguments of the actions' corresponding function

proposeAction

Proposed actions require a minimum delay of 24 hours before their execution. This is done such that a compromised admin cannot drain the company's funds instantaneously. Rather, the action can be canceled by another admin within the 24-hour window. Once the delay has passed, any account can execute the action. Actions that include a delay are:

  • Changing an employee's cash salary

  • Extending an employee's equity payments

  • Setting a new equity payment schedule for an employee

  • Paying cash directly to an employee

  • Paying equity directly to an employee This function will revert if the action has already been proposed.

function proposeAction(bytes4 action, bytes memory params) public virtual onlyAdmin pausable returns (bytes32);

Parameters

NameTypeDescription

action

bytes4

The action to be executed, represented as the selector of the function

params

bytes

The abi-encoded parameters of the action.

Returns

NameTypeDescription

<none>

bytes32

The ID of the action, which is the hash of the action and its parameters

cancelPendingAction

Cancels a pending action. Only callable by an admin

function cancelPendingAction(bytes32 actionId) external;

Parameters

NameTypeDescription

actionId

bytes32

The ID of the action to be canceled

changeCashSalary

Changes the cash salary of the employee with 'tokenId' to 'newCashSalary'

function changeCashSalary(uint256 tokenId, uint256 newCashSalary) external virtual requireEmployed(tokenId) pausable;

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

newCashSalary

uint256

The new annual cash salary of the employee

extendEquityPayments

Extends the equity payments of 'tokenId' by 'equityExtension' and increases the duration by 'durationExtension'

Must be proposed by an admin and executed after a 24 hour delay This function will revert if the vesting schedule associated with 'tokenId' is non-existent, which can occur if the employee never had a vesting schedule to begin with or their vesting schedule has been completed

function extendEquityPayments(
    uint256 tokenId,
    uint256 equityExtension,
    uint32 durationExtension,
    bytes memory employeeSignature
) external virtual requireEmployed(tokenId) pausable;

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

equityExtension

uint256

The number of shares to be added to the employee's equity payments

durationExtension

uint32

The number of seconds to be added to the employee's vesting schedule

employeeSignature

bytes

The signature of the employee. The employee should sign the hash of 'getEquityExtensionTransactionHash' The employee signature is needed such that an employer cannot arbitrarily increase the of an employee's vesting schedule without the consent of the employee

setNewEquityPayments

Sets a new equity payment schedule for 'tokenId'

Must be proposed by an admin and executed after a 24 hour delay This function will revert if 'tokenId' has an existing vesting schedule

function setNewEquityPayments(uint256 tokenId, DataTypes.NewEquityPaymentsData memory data)
    external
    virtual
    requireEmployed(tokenId)
    nonReentrant
    pausable
    returns (uint256);

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

data

DataTypes.NewEquityPaymentsData

The new equity payment data as a DataTypes.NewEquityPaymentsData struct

- data.equityAmount: The total number of shares to be paid to the employee

- data.startDate: The start date of the employee, represented as a unix timestamp

- data.vestingDuration: The number of seconds that it will take for the employee to be able to claim all of their shares

- data.vestingCliff: The number of seconds that it will take for the employee to be able to claim their first shares

- data.vestingInitialUnlock: The number of shares that the employee will be able to claim at the end of their cliff

payCash

Pays 'cashPaymentAmount' to the recipient of 'tokenId' in the cash asset of the employee, which should be the company's denomination asset. The recipient may be different from the employee, themself

Must be proposed by an admin and executed after a 24 hour delay 'cashPaymentAmount' cannot exceed $10 million

function payCash(uint256 tokenId, uint256 cashPaymentAmount, bytes32 encodedDetails)
    external
    virtual
    requireEmployed(tokenId)
    pausable
    returns (uint256);

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

cashPaymentAmount

uint256

The amount of cash to be paid to the employee

encodedDetails

bytes32

Details regarding the cash payment as a bit-field. Must be either encoded to include either or both: DataTypes.PaymentDetails.Bonus or DataTypes.PaymentDetails.ContractorPayment. 'Encoding' is done by shifting left 1 by the value of the Payment Details enum

Returns

NameTypeDescription

<none>

uint256

The amount paid, which may be different than the 'cashPaymentAmount' due to ERC20 transfer fees

payEquity

Pays 'equityPaymentAmount' to the recipient of 'tokenId' in the form of the company's common shares

Must be proposed by an admin and executed after a 24 hour delay 'equityPaymentAmount' cannot exceed the treasury common shares

function payEquity(uint256 tokenId, uint256 equityPaymentAmount, bytes32 encodedDetails)
    external
    virtual
    requireEmployed(tokenId)
    pausable;

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

equityPaymentAmount

uint256

The amount of equity to be paid to the employee

encodedDetails

bytes32

Details regarding the equity payment as a bit-field. Must be either encoded to include either or both: DataTypes.PaymentDetails.Bonus or DataTypes.PaymentDetails.ContractorPayment. 'Encoding' is done by shifting left 1 by the value of the Payment Details enum

updateEmployeeDocument

Updates the document related to the employee with 'tokenId' in the Document Registry. Only callable by the board or an admin

function updateEmployeeDocument(uint256 tokenId, string memory updatedDocumentName, string memory updatedDocumentUri)
    external
    virtual
    onlyAdmin
    returns (uint256);

Returns

NameTypeDescription

<none>

uint256

The new version of the document

collectibleCash

Returns the amount of cash collectible by 'tokenId'

This function will revert if 'tokenId' has not been minted

function collectibleCash(uint256 tokenId) public view virtual returns (uint256);

collectibleEquity

Returns the amount of equity collectible by 'tokenId'

This function will revert if 'tokenId' has not been minted

function collectibleEquity(uint256 tokenId) public view virtual returns (uint256);

isEmployee

Returns true if 'tokenId' is a current employee, false otherwise

This function will revert if 'tokenId' has not been minted. It will return false if the employee's end date has passed. An end date is set during an employee's termination or resignation

function isEmployee(uint256 tokenId) public view virtual returns (bool);

getSalary

Returns the salary of 'tokenId' in the form of a DataTypes.Salary struct

To query the vesting schedule of the employee, call 'getVestingSchedule()' of the company's token timelock with the salary's 'tokenTimelockTokenId'

function getSalary(uint256 tokenId) public view returns (DataTypes.Salary memory);

hashAction

Returns the action id of an 'action' given its abi-encoded 'params'

function hashAction(bytes4 action, bytes memory params) public pure virtual returns (bytes32);

isActionReady

Returns true if the pending action associated with 'actionId' is ready to execute, false otherwise

Will return false instead of reverting, even if the pending action does not exist

function isActionReady(bytes32 actionId) public view virtual returns (bool);

isPendingAction

Returns true if the pending action associated with 'actionId' exists, false otherwise

function isPendingAction(bytes32 actionId) public view returns (bool);

getPendingActions

Returns the action IDs of all outstanding pending actions as a bytes32 array

function getPendingActions() public view returns (bytes32[] memory);

getPendingActionSnapshot

Returns the snapshot of the pending action associated with 'actionId' The snapshot is the unix timestamp at which the action can be executed

function getPendingActionSnapshot(bytes32 actionId) public view returns (uint256);

getHireTransactionHash

Returns the transaction hash of the encoded hire data

This hash is to be signed by the employee before being hired

function getHireTransactionHash(
    address employee,
    uint256 annualCash,
    uint256 equityAmount,
    uint48 startDate,
    uint32 vestingDuration,
    uint32 vestingCliff,
    uint256 vestingInitialUnlock,
    uint256 employeeNonce
) public view returns (bytes32);

Parameters

NameTypeDescription

employee

address

The employee to hire

annualCash

uint256

The annual cash salary of the employee

equityAmount

uint256

The total number of shares to be paid to the employee over 'vestingDuration'

startDate

uint48

The start date of the employee, represented as a unix timestamp

vestingDuration

uint32

The number of seconds that it will take for the employee to be able to claim all of their shares

vestingCliff

uint32

The number of seconds that it will take for the employee to be able to claim their first shares

vestingInitialUnlock

uint256

The number of shares that the employee will be able to claim at the end of their cliff

employeeNonce

uint256

The nonce of the employee, which is used to prevent replay attacks

_getHireStructHash

Encodes hire data as a struct hash following the EIP-712 encoding standards

Reference: https://eips.ethereum.org/EIPS/eip-712

function _getHireStructHash(
    address employee,
    uint256 annualCash,
    uint256 equityAmount,
    uint48 startDate,
    uint32 vestingDuration,
    uint32 vestingCliff,
    uint256 vestingInitialUnlock,
    uint256 employeeNonce
) internal pure returns (bytes32);

getResignTransactionHash

Returns the transaction hash of the encoded resign data

This hash is to be signed by the employee when resigning via signature

function getResignTransactionHash(uint256 tokenId, uint256 endDate, uint256 employeeNonce)
    public
    view
    returns (bytes32);

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

endDate

uint256

The date that the employee will stop earning their salary

employeeNonce

uint256

The nonce of the employee, which is used to prevent replay attacks

_getResignStructHash

Encodes resign data as a struct hash following the EIP-712 encoding standards

Reference: https://eips.ethereum.org/EIPS/eip-712

function _getResignStructHash(uint256 tokenId, uint256 endDate, uint256 employeeNonce)
    internal
    pure
    returns (bytes32);

getEquityExtensionTransactionHash

Returns the transaction hash of the encoded equity extension data

This hash is to be signed by the employee when extending their equity payments

function getEquityExtensionTransactionHash(
    uint256 tokenId,
    uint256 equityExtension,
    uint32 durationExtension,
    uint256 employeeNonce
) public view returns (bytes32);

Parameters

NameTypeDescription

tokenId

uint256

The ID of the payroll token

equityExtension

uint256

The total number of shares to be paid to the employee over 'vestingDuration'

durationExtension

uint32

The number of seconds to be added to the employee's vesting schedule

employeeNonce

uint256

The nonce of the employee, which is used to prevent replay attacks

_getEquityExtensionStructHash

Encodes equity extension data as a struct hash following the EIP-712 encoding standards

Reference: https://eips.ethereum.org/EIPS/eip-712

function _getEquityExtensionStructHash(
    uint256 tokenId,
    uint256 equityExtension,
    uint32 durationExtension,
    uint256 employeeNonce
) internal pure returns (bytes32);

name

Returns the name of the Payroll Manager

function name() public view virtual override(ERC721Upgradeable, IPayrollManager) returns (string memory);

symbol

Returns the symbol of the Payroll Manager

function symbol() public view virtual override(ERC721Upgradeable, IPayrollManager) returns (string memory);

version

Returns the version of the implementation as a uint256

function version() public pure virtual override(Credentialed, ICredentialed) returns (uint256);

coreId

Returns the coreId of the implementation as a bytes32

The core ID is the keccak256 hash of the contract name followed by a version under the following syntax: "mezzanine.coreId.ContractName.vX" For example, the core ID of the 2nd version of the Treasury would be the following: keccak256(abi.encodePacked("mezzanine.coreId.Treasury.v2"))

function coreId() public pure virtual override(Credentialed, ICredentialed) returns (bytes32);

_validateSignature

function _validateSignature(address signer, bytes32 txHash, bytes memory signature) internal view;

_validateAreEmployees

function _validateAreEmployees(uint256[] memory tokenIds) internal view virtual;

_validateIsEmployee

function _validateIsEmployee(uint256 tokenId) internal view virtual;

_getAddressesForEmployees

Returns the addresses of employees for a given set of token IDs

function _getAddressesForEmployees(uint256[] memory tokenIds) internal view returns (address[] memory);

_getCollectibleCashForEmployees

Returns the collectible cash for a given set of token IDs

function _getCollectibleCashForEmployees(uint256[] memory tokenIds) internal view returns (uint256[] memory);

_getTokenTimelock

function _getTokenTimelock() internal view returns (ITokenTimelock);

_getCommonShares

function _getCommonShares() internal view returns (address);

approve

Overridden ERC721 approve() such that approvals are disabled

function approve(address, uint256) public virtual override(ERC721Upgradeable, IERC721);

transferFrom

Overridden ERC721 transferFrom() such that transfers are disabled

function transferFrom(address, address, uint256) public virtual override(ERC721Upgradeable, IERC721);

_update

Overridden ERC721 update such that burning is disabled

function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address);

_setApprovalForAll

function _setApprovalForAll(address, address, bool) internal view virtual override;

supportsInterface

ERC165 support

function supportsInterface(bytes4 interfaceId)
    public
    view
    virtual
    override(ERC721Upgradeable, MezzEIP712, MezzUUPSUpgradeable, IERC165)
    returns (bool);

onERC721Received

Reference: https://eips.ethereum.org/EIPS/eip-721

function onERC721Received(address, address, uint256, bytes memory) public view virtual returns (bytes4);

upgradeToNewerVersion

Upgrades 'this' to a newer version via the Mezz Migrator. Only callable by the Treasury, whose signers are the board of directors

Will revert if the protocol state is 'Paused' or 'Frozen'

function upgradeToNewerVersion(uint256 newVersion, bytes memory data) public virtual onlyProxy onlyBoard;

Parameters

NameTypeDescription

newVersion

uint256

The new version to upgrade to

data

bytes

The data to be passed to the new implementation, which likely should be a reinitializer function if used

_authorizePatch

Access control for 'resetToPatchedLatestVersion()'

function _authorizePatch(bytes memory) internal view virtual override;

Structs

PayrollManagerStorage

struct PayrollManagerStorage {
    EnumerableSet.Bytes32Set _pendingActions;
    mapping(bytes32 => uint256) _snapshotByPendingActionId;
    mapping(uint256 => DataTypes.Salary) _salaryByTokenId;
}

Last updated