Skip to main content

Overview

Security Token smart contract implementation from CoMakery (dba Upside). The core purpose of the token is to enforce transfer restrictions for certain groups.

This implementation attempts to balance simplicity and sufficiency for smart contract security tokens that need to comply with regulatory authorities - without adding unnecessary complexity for simple use cases. It implements the ERC-20 token standard with ERC-1404 security token transfer restrictions.

This approach takes into account yet to be standardized guidance from ERC-1400 (which has additional recommendations for more complex security token needs) and ERC-1404 which offers an approach similar to ERC-902. Unfortunately ERC-1404 does not adopt ERC-1066 standard error codes - which this project may adopt in the future. Since no security token standards have reached mass adoption or maturity and they do not fully agree with each other, the token optimizes for a simple and sufficient implementation.

Simplicity is desirable so that contract functionality is clear. It also reduces the number of smart contract lines that need to be secured (each line of a smart contract is a security liability).

Disclaimer

This open or closed source software is provided with no warranty. This is not legal advice. CoMakery (dba Upside) is not a legal firm and is not your lawyer. Securities are highly regulated across multiple jurisdictions. Issuing a security token incorrectly can result in financial penalties or jail time if done incorrectly. Consult a lawyer and tax advisor. Conduct an independent security audit of the code.

On-Chain Holder/Wallet Management

Active Holder/Wallet management is conducted on-chain and autonomously, with multiple admin configurations that allow flexibility of transfer rule configuration. This greatly simplifies the off-chain accounting and effort required from Transfer (and other) Admins, removing the need to track Wallet holdings across different Holders.

Wallets are not programatically prohibited from assuming multiple Admin roles. We advise against this in practice; however, this must be enforced off-chain.

Holders are allowed to keep multiple Wallet addresses, which can be spread across multiple Transfer Groups (in which case, they would be added to each group's holder count) as well as within Transfer Groups. These Wallets are consolidated under a common holderId.

  • Ex.: Holder A can have 4 wallets spread across two Transfer Groups, X and Y. The Holder can have Wallets 1 & 2 in Group X, and Wallets 3 & 4 in Group Y. They will still count overall as one single Holder, but it will also be considered as a unique holder in Group X and one unique holder in Group Y.

To manage these Holders and their Wallets:

  • A hook has been overridden (_afterTokenTransfer) for mint/transfer/burn/freeze functions, automatically checking that recipient Wallet addresses of tokens are catalogued and assigned holderIds as needed.
  • If a "new" Wallet address receives a token, a new holderId is created for that Wallet address.
  • Admins can also separately create Holders from Wallet addresses and append Wallet addresses to existing Holder accounts

Transfer Restrictions

The Security Token can be configured after deployment to enforce transfer restrictions such as the ones shown in the diagram below. Each Holder's blockchain Wallet address corresponds to a different group.

This is enforced in TransferRules.sol.

Example

Only transfers between wallet address groups in the direction of the arrows are allowed:

Here's an example overview of how transfer restrictions could be configured and enforced.

The Transfer Admin for the Token Contract can provision wallet addresses to transfer and receive tokens under certain defined conditions. This is the process for configuring transfer restrictions and executing token transfers:

  1. An Investor sends their Anti Money Laundering and Know Your Customer (AML/KYC) information to the Transfer Admin or to a proxy vetting service off-chain to verify this information.

    • The benefit of using a qualified third party provider is to avoid needing to store privately identifiable information.
    • We recommend implementations use off-chain mechanisms (such as a 3rd party AML/KYC provider) that ensure a given address is approved and is a non-malicious smart contract wallet. However, generally multi-signature type wallets must be allowed in order to provide adequate security for investors.
    • This smart contract implementation does not provide a solution for collecting AML/KYC information.
  2. The Transfer Admin or Wallet Admin calls setAddressPermissions(buyerAddress, transferGroup, freezeStatus) to provision their account. Initially this will be done for the Primary Issuance of tokens to Investors where tokens are distributed directly from the Issuer to Holder wallets, where:

    • buyerAddress: Buyer/Investor wallet address for which to set permissions
    • transferGroup: desired transfer group ID for wallet address
    • freezeStatus: boolean flag signifying whether the address is frozen from executing transfers (if set to true)
  3. A potential Investor sends their AML/KYC information to the Transfer Admin or Wallet Admin or a trusted AML/KYC provider.

  4. The Transfer Admin or Wallet Admin calls setAddressPermissions(buyerAddress, transferGroup, freezeStatus) to provision the Investor account.

  5. At this time (or potentially earlier), the Transfer Admin or Wallet Admin authorizes the transfer of tokens between account groups with setAllowGroupTransfer(fromGroup, toGroup, afterTimestamp). Note that allowing a transfer from group A to group B by default does not allow the reverse transfer from group B to group A. This would need to be configured separately.

    • Ex.: Reg CF unaccredited Investors may be allowed to sell to Accredited US Investors but not vice versa.

WARNING: Maximum Total Supply, Minting and Burning of Tokens

The variable maxTotalSupply is set when the contract is created and limits the total number of tokens that can be minted.

Reserve Admins can mint tokens to and burn tokens from any address. This is primarily to comply with law enforcement, regulations and stock issuance scenarios - but this centralized power could be abused. Transfer Admins, authorized by Contract Admins, can also update the transfer rules at any moment in time as many times as they want.

Overview of Transfer Restriction Enforcement Functions

FromToRestrictEnforced ByAdmin Role
Reg D/S/CFAnyoneUntil TimeLock endsfundReleaseSchedule(investorAddress, balanceReserved, commencementTime, scheduleId, cancelableByAddresses)Any Admin
Reg S GroupUS AccreditedForbidden During Flowback Restriction PeriodsetAllowGroupTransfer(fromGroupS, toGroupD, afterTime)Transfer Admin
Reg S GroupReg S GroupForbidden Until Shorter Reg S TimeLock EndedsetAllowGroupTransfer(fromGroupS, toGroupS, afterTime)Transfer Admin
IssuerReg CF with > maximum number of total holders allowedForbid transfers increasing number of total Holders (across all groups) above a certain thresholdsetHolderMax(maxAmount)Transfer Admin
IssuerReg CF with > maximum number of Holders per group allowedForbid transfers increasing number of total Holders (within each group) above a certain thresholdsetHolderGroupMax(transferGroupID, maxAmount)Transfer Admin
Stolen TokensAnyoneFix With Freeze, Burn, Reissuefreeze(address, isFrozenFlag);
burn(address, amount);
mint(newOwnerAddress);
Wallets Admin or Transfer Admin can freeze() and Reserve Admin can do mint() burn()
Any Address During Regulatory FreezeAnyoneForbid all transfers while pausedpause(isPausedFlag)Transfer Admin
Any Address During Regulatory FreezeAnyoneUnpause from a paused statepause(isPausedFlag)Transfer Admin
AnyoneAnyoneForce the transfer of tokens for emergenciesforceTransferBetween(sender, recipient, amount)Reserve Admin

Roles

The smart contract enforces specific admin roles. The roles divide responsibilities to reduce abuse vectors and create checks and balances. Ideally each role should be managed by a separate admin with separate key control.

In some cases, such as for the Contract Admin or Wallets Admin, it is recommended that the role's private key is managed through multi-signature (e.g. requiring 2 of 3 or N of M approvers) authentication.

Admin Types

The Admin functionality breaks down into 4 main roles. The contract is configured to expect these wallets. In the contract constructor:

  • Contract Admin
    • Akin to root level access admininstrator. Can upgrade internal contract dependencies (ie RestrictedSwap, TransferRules) or grant Admin permissions. Recommended to be a secure multi-sig wallet.
  • Reserve Admin
    • Receives initial tranche of minted tokens from deployment. Also can adjust the supply of tokens by minting or burning or forcibly initiate transfers.

Via granted roles (from Contract Admin):

  • Transfer Admin
    • Can set transfer restriction permissions/rules between groups or invoke snapshots.
    • Transfer Admin capabilities are a superset of those of Wallets Admin.
  • Wallets Admin
    • Can manage Holder/Wallet transfer group assignments.

Typically any legal entity third-party Transfer Agent will need access to both the roles for Transfer Admin and Wallets Admin. However some agents (such as exchanges) will, for example, be able to assign groups to wallets and permission them (as a Wallets Admin) but will not be able to adjust the transfer rules.

Admin Functionality

FunctionContract AdminReserve AdminTransfer AdminWallets Admin
upgradeTransferRules()yesnonono
snapshot()yesnonono
mint()noyesnono
burn()noyesnono
forceTransferBetween()noyesnono
pause() or unpause (ie pause(false))nonoyesno
setMinWalletBalance()nonoyesno
setAllowGroupTransfer()nonoyesno
setHolderMax()nonoyesno
setHolderGroupMax()nonoyesno
fundDividend()nonoyesno
setAddressPermissions()nonoyesyes
freeze()nonoyesyes
setTransferGroup()nonoyesyes
createHolderFromAddress()nonoyesyes
appendHolderAddress()nonoyesyes
addHolderWithAddresses()nonoyesyes
removeHolder()nonoyesyes
createReleaseSchedule()yesyesyesyes
batchFundReleaseSchedule()yesyesyesyes
fundReleaseSchedule()yesyesyesyes

Use Cases

Initial Security Token Deployment

  1. The Deployer configures the parameters and deploys the smart contracts to a public EVM blockchain. At the time of deployment, the deployer configures a separate Reserve Admin address, a Transfer Admin address, and a Wallets Admin address. This allows the reserve security tokens to be stored in cold storage since the treasury Reserve Admin address private keys are not needed for everyday use by the Transfer Admin.
  2. The Reserve Admin then provisions a Wallets Admin address for distributing tokens to investors or other stakeholders. The Wallets Admin uses setAddressPermissions(investorAddress, transferGroup, freezeStatus) to set address restrictions.
  3. The Transfer Admin authorizes the transfer of tokens between account groups with setAllowGroupTransfer(fromGroup, toGroup, afterTimestamp) .
  4. The Reserve Admin then transfers tokens to the Wallets Admin address.
  5. The Wallets Admin then transfers tokens to Investors or other stakeholders who are entitled to tokens.

Setup For Separate Issuer Private Key Management Roles

By default the reserve tokens cannot be transferred to. To allow transfers the Transfer Admin or Wallets Admin must configure transfer rules using both setAddressPermissions(account, ...) to configure the individual account rules and setAllowGroupTransfer(...) to configure transfers between accounts in a group. A group represents a category like US accredited investors (Reg D) or foreign investors (Reg S).

During the setup process to split transfer oversight across three private key holders, the Transfer Admin can setup rules that only allow the Reserve Admin group to only transfer tokens to the Wallets Admin address group. The Wallets Admin should be restricted to a limited maximum balance necessary for doing one batch of token distributions - rather than the whole reserve. The use of a hot wallet Wallets Admin for small balances also makes everyday token administration easier without exposing the issuer's reserve of tokens to the risk of total theft in a single transaction. Each of these private keys may also be managed with a multi-sig solution for added security.

Multi-sig is especially important for the token Reserve Admin and Contract Admin.

Here is how these restricted Admin accounts can be configured:

  1. Transfer Admin, Reserve Admin and Wallets Admin accounts are managed by separate users with separate keys. For example, separate Nano Ledger S hardware wallets.
  2. Reserve and Wallets Admin addresses can have their own separate transfer groups.
    • setAddressPermissions(reserveAdminAddress, reserveAdminTransferGroup, freezeStatus)
    • setAddressPermissions(walletsAdminAddress, walletsAdminTransferGroup, freezeStatus)
  3. Reserve Address Group can only transfer to Wallets Admin Groups after a certain Timestamp.
    • setAllowGroupTransfer(reserveAdminTransferGroup, walletsAdminTransferGroup, afterTimestamp)
  4. Wallets Admin Address can transfer to investor groups like Reg D and Reg S after a certain Timestamp.
    • setAllowGroupTransfer(walletsAdminTransferGroup, regD_TransferGroup, afterTimestamp)
    • setAllowGroupTransfer(walletsAdminTransferGroup, regS_TransferGroup, afterTimestamp)

Then the Wallets Admin can distribute tokens to investors and stakeholders as described below...

Issuing the Token To AML / KYC'd Recipients

  1. The Transfer Admin gathers AML/KYC and accreditation information from investors and stakeholders who will receive tokens directly from the Issuer (the Primary Issuance).

  2. (optional) set the Holder max if desired

  3. Transfer Admin configures approved Transfer Group for Wallets Admin.

  4. Transfer Admin then configures approved Transfer Groups for Investor and stakeholders with setAddressPermissions(address, transferGroup, freezeStatus). Based on the AML/KYC and accreditation process the investor can provision the account address with:

    a) a transfer group designating a regulatory class like "Reg D", "Reg CF" or "Reg S"

    b) the freeze status of that address (with true meaning the account will be frozen from activity)]

  5. The Transfer Admin then must allow transfers between groups with setAllowGroupTransfer(walletsAdminGroup, investorAdminGroup...)

  6. The tokens can then be transferred from the Issuer's wallet to the provisioned addresses.

  7. Transfer Restrictions are detected.

Note that there are no transfers initially authorized between groups. By default no transfers are allowed between groups - all transfer groups are restricted.

Lockup Periods

Lockup periods are enforced via:

  • setAllowGroupTransfer(fromGroup, toGroup, unixTimestamp) allows transfers from one Transfer Group to another after the unixTimestamp. If the unixTimestamp is 0, then no transfer is allowed.

Maximum Number of Holders Allowed

By default Transfer Groups cannot receive token transfers. To receive tokens the issuer gathers AML/KYC information and then calls setAddressPermissions().

A single Holder may have unlimited Wallet addresses. This cannot be altered. The Issuer can only configure the maximum number of Holders allowed (via Transfer Admin) by calling setHolderMax. By default, this limit is set to 2**255-1.

setHolderMax(amount)

Maximum Number of Holders Allowed Per Group

Transfer Admin can configure the maximum number of allowed Holders per group by calling setHolderGroupMax. By default, the holderGroupMax for each group is set to 0, meaning that it won't be applied as a restriction.

Group 0 (the default Holder group) is the only group for which the holder group max variable cannot be applied (an unlimited number of Group 0 Holders are allowed in perpetuity). It is also the only group for which minWalletBalance cannot be applied (Holders with zero balance are allowed to remain in the group).

setHolderGroupMax(groupID, amount)

Minimum Allowed Wallet Balance

Transfer Admin can configure global minimum wallet balances by calling setMinWalletBalance. This prevents wallets from ending up with a negligible number of tokens that unncessarily occupies Holder allocations by reverting transactions that do so.

setMinWalletBalance(amount)

setTransferGroup vs setAddressPermissions

setAddressPermissions is very similar to setTransferGroup, but contains a third argument with frozen status. This status can allow or deny transfers to the provided user.

setAddressPermissions is usually used after KYC/AML verification to activate a particular wallet for transfers. So in cases where you do not need to change the frozen status, you should use setTransferGroup. The frozen status restrictions described in the TransferRules contract.

Timelock Cancellations and Transfers

In order to cancel or transfer timelocks, the Token address itself must be whitelisted for transfers.

Cancel Timelock

In the case of cancellations, the Token address must be in a group able to transfer tokens to 1) the reclaim address and 2) the initial target recipient of the original vesting. The funder of the original vesting must also be in a group able to transfer tokens to the reclaim address.

Timelocks can be configured to be cancelled by a specific canceler address. This canceler can designate a separate reclaim address to receive the locked token portion of the remaining timelock, pending allowed group transfers as described above. The remaining unlocked portion will be transferred directly to the initial target recipient of the original vesting.

Transfer Timelock

For unlocked tokens within a timelock, the initial target recipient can choose to transfer unlocked tokens directly to another recipient. This is a convenience atop the typical transfer method.

In the case of a timelock transfer, the Token address must be in a group able to transfer tokens to the new recipient of the tokens. The initial target recipient must also be in a group able to transfer tokens to the new recipient.

Example Transfer Restrictions

Example: Investors Can Trade With Other Investors In The Same Group (e.g. Reg S)

To allow trading in a group:

  • Call setAddressPermissions(address, transferGroup, freezeStatus) for trader Wallets in the group
  • setAllowGroupTransfer(fromGroupX, toGroupX, groupTimeLock) for Wallets associated with groupIDs (for example, Reg S)
  • A token transfer for an allowed group will succeed if:
    • the timelock conditions have passed
    • the recipient of a token transfer does not result in a total holder count in a given group that exceeds the defined holderGroupMax (if configured, ie holderGroupMax set to > 0)
    • the recipient of a token transfer does not result in a total global holder count that exceeds the defined holderMax
    • the recipient and sender both do not end up with a balance in their wallet less than the defined minWalletBalance (if configured, ie minWalletBalance set to > 0), unless it is 0 (wallets are allowed to be empty).

Example: Avoiding Flowback of Reg S "Foreign" Assets

To allow trading between Foreign Reg S account addresses but forbid flow back to US Reg D account addresses until the end of the Reg D lockup period

  • Call setAddressPermissions(address, groupIDForRegS, freezeStatus) to configure settings for Reg S investors
  • Call setAddressPermissions(address, groupIDForRegD, freezeStatus) to configure settings for Reg D investors
  • setAllowGroupTransfer(groupIDForRegS, groupIDForRegS, addressTimelock) allow Reg S trading
  • A token transfer for between allowed groups will succeed if:
    • the addressTimelock time has passed; and
    • the recipient of a token transfer is not frozen (ie freezeStatus is false).

Example: Exchanges Can Register Omnibus Accounts

Centralized exchanges can register custody addresses using the same method as other users. They contact the Issuer to provision accounts and the Transfer Admin or Wallets Admin calls setAddressPermissions() for the exchange account.

When customers of the exchange want to withdraw tokens from the exchange account they must withdraw into an account that the Transfer Admin has provisioned for them with setAddressPermissions().

Talk to a lawyer about when exchange accounts may or may not exceed the maximum number of holders allowed for a token.

Group 0 Transfer Restrictions

In general, it is possible to allow Group 0 transfers. They are configurable in the same way that other transfer restrictions across different groups can be configured. This allowance is required for certain real-world situations. For example, if all tokens are freely tradeable under Reg A+ or a public filing, then Group 0 transfers can be allowed.

Transfers Can Be Paused To Comply With Regulatory Action

If there is a regulatory issue with the token, all transfers may be paused by the Transfer Admin calling pause(). During normal functioning of the contract pause() should never need to be called.

Note: This pause() mechanism has been implemented into the RestrictedLockupToken and RestrictedSwap contracts but not the Dividends contract. As pauses are intended for emergency situations; and dividends, once funded, belong fully to the recipient (even if locked), the pause mechanism has been excluded so as to prevent potential pause abuse from circumventing rightful dividends distributions.

Recovery From A Blockchain Fork

Issuers should have a plan for what to do during a blockchain fork. Often security tokens represent a scarce off chain asset and a fork in the blockchain may present ambiguity about who can claim an off chain asset. For example, if 1 token represents 1 ounce of gold, a fork introduces 2 competing claims for 1 ounce of gold.

In the advent of a blockchain fork, the issuer should do something like the following:

  • have a clear and previously defined way of signaling which branch of the blockchain is valid
  • signal which branch is the system of record at the time of the fork
  • call pause() on the invalid fork (Transfer Admin)
  • use burn() and mint() to fix errors that have been agreed to by both parties involved or ruled by a court in the issuers jurisdiction (Reserve Admin)

Law Enforcement Recovery of Stolen Assets

In the case of stolen assets with sufficient legal reason to be returned to their owner, the issuer can call freeze() (Wallets Admin, Transfer Admin), burn(), and mint() (Reserve Admin) to transfer the assets to the appropriate account.

Although this is not in the spirit of a cryptocurrency, it is available as a response to requirements that some regulators impose on blockchain security token projects.

Asset Recovery In The Case of Lost Keys

In the case of lost keys with sufficient legal reason to be returned to their owner, the issuer can call freeze(), burn(), and mint() to transfer the assets to the appropriate account. This opens the issuer up to potential cases of fraud. Handle with care.

Once again, although this is not in the spirit of a cryptocurrency, it is available as a response to requirements that some regulators impose on blockchain security token projects.

Swap

Swap functionality is described in RestrictedSwap.sol.

RestrictedSwap provides a secure means of swapping tokens between known holders of the primary Restricted token and any payment ERC-20 tokens (ie USDC, DAI, etc).

Note: Swap is not intended between two Restricted tokens. Rather, it is intended as a purchase of Restricted tokens using ERC-20 tokens (AKA payment token or quote token). This is enforced in the swap contract itself, by preventing ERC-1404 compatible tokens (checked via ERC-165 interface support) from being used as payment. However, other types of Restricted tokens may not explicitly support the ERC-1404 interface and thus cannot be checked programmatically; therefore, this functionality should be used with caution and payment token types should always be verified first.

How It Works

As a prerequesite, users of the swap functionality are assumed to have passed any necessary off-chain AML/KYC and deemed valid holders. Contracts are also assumed to have been configured properly with admin-granted roles and transfer restrictions in place. In particular, transfers must be allowed between the groups of Seller (of Restricted token) and Buyer.

Configuring a Purchase of Restricted Token

As a holder (Buyer) of an ERC-20 payment token (ie USDC, DAI, etc), one can configure a purchase order for a specified amount of Restricted token with a known party (Seller). Here is an example involving USDC:

  1. Buyer must know in advance specifically how much Restricted token they'd like to receive, how much USDC they are willing to pay, and the Seller on the other end.
  2. Buyer must approve the Swap contract itself to handle their USDC in the purchase amount required.
  3. Buyer then configures an open purchase order by calling configureBuy with the following parameters. A Swap ID is emitted in an event.
    • amount of Restricted token desired
    • address of Seller
    • amount of payment token (USDC) willing to swap
    • address of payment token contract
  4. Seller must approve the Swap contract itself to handle their Restricted token in the sell amount required by the configured swap.
  5. Seller can complete this order by calling completeSwapWithRestrictedToken with the emitted Swap ID.

When transfer restrictions are validated between Buyer and Seller, the swap is completed and assets are actually transferred between transacting parties.

Configuring a Sale of Restricted Token

As a holder of the Restricted token, one can also configure a sell order for a specified amount of desired payment ERC-20 token. It is extremely similar to the example above, except terms of the sale must be specified in advance. Here is an example involving USDC:

  1. Seller must know in advance specifically how much Restricted token they'd like to sell, how much USDC they require, and the Buyer on the other end.
  2. Seller must approve the Swap contract itself to handle their Restricted token in the sell amount desired.
  3. Seller then configures an open sell order by calling configureSell with the following parameters. A Swap ID is emitted in an event.
    • amount of Restricted token to sell
    • address of Buyer
    • amount of payment token (USDC) required
    • address of payment token contract
  4. Buyer must approve the Swap contract itself to handle their USDC in the amount specified by the configured swap.
  5. Buyer can complete this order by calling completeSwapWithQuoteToken with the emitted Swap ID.

When transfer restrictions are validated between Buyer and Seller, the swap is completed and assets are actually transferred between transacting parties.

Lockup

A "vesting" smart contract that can implement token lockup with scheduled release and distribution:

  • Can enforce a scheduled release of tokens (e.g. investment lockups)
  • Smart contract enforced lockup schedules are used to control the circulating supply and can be an important part of tokenomics.
  • Some lockups are cancelable - such as employee vestings. When canceled, unlocked tokens remain in the recipients account address and locked tokens are returned to an address specified by the administrator that has an appropriate transfer group to receive the tokens.

Note that tokens in lockups cannot be burned by admins to avoid significant complexity.

This is described in RestrictedLockupToken.sol.

Dividends

Provides the possibility of making payments between the owners of the Primary coin at a certain point in time.

Each moment of time is recorded as a snapshot (using snapshot() method manually by Contract Admin).

All payments are made as a percentage of the snapshot balances.

Any ERC-20 tokens are supported for dividend payment.

The snapshot mechanism uses the ERC-20 extension - OpenZeppelin/ERC20Snapshot.

This is described in Dividends.sol.

Distribution Rounding Error

In certain unique configurations of dividends, exact division is impossible (ie fractional remainders). For example, this occurs if there are 9 dividend tokens funded, to be split amongst 5 holders of equal balances.

Dividends are disbursed in such a way to ensure no tokens remain locked in the contract in these cases. They are split successively according to each claimant's proportional share of outstanding dividends. The last claimant is therefore guaranteed 100% of remaining tokens.

Access Control

To decreasing the contract size we provide an optimized version of Access Control. This is similar to the linux file permission bitmask exposed in the chmod command line function.

Our implementation uses binary bitmask determine the access control roles for an address. This optimizes the gas cost and the size of the smart contract code itself.

This is described in EasyAccessControl.sol.

How it works

We use a uint8 binary representation of a number, such as 01010101 to represent the roles IDs within the access controls.

Roles are defined by a specific bit position in the bit storage representation.

As example decimal 1 is 00000001 in binary mode, 2 is 00000010, 3 is 00000011, 4 is 00000100, 7 is 00000111 etc.

We describe the roles in use as:

uint8 constant CONTRACT_ADMIN_ROLE = 1; // 0001
uint8 constant RESERVE_ADMIN_ROLE = 2; // 0010
uint8 constant WALLETS_ADMIN_ROLE = 4; // 0100
uint8 constant TRANSFER_ADMIN_ROLE = 8; // 1000

If you want to use the unused bits in the future to add new roles you can add something like

uint8 constant NEW_ROLE = 16; // 000010000
uint8 constant SECOND_ROLE = 32; // 000100000
...etc

You can grant multiple roles by adding the role number values together to get the correct bitmask representation like this:

uint8 constant WALLET_AND_TRANSFER_ADMIN_ROLE = 12; // 0001100

or

uint8 constant WALLET_AND_TRANSFER_ADMIN_ROLE = WALLETS_ADMIN_ROLE | TRANSFER_ADMIN_ROLE; // 0001100

For manipulating binary numbers you can use binary operators *&, | and ^.

Example:

uint8 constant CONTRACT_ADMIN_ROLE = 1; // 0001
uint8 constant RESERVE_ADMIN_ROLE = 2; // 0010
uint8 constant WALLETS_ADMIN_ROLE = 4; // 0100
uint8 constant TRANSFER_ADMIN_ROLE = 8; // 1000

WALLETS_ADMIN_ROLE | TRANSFER_ADMIN_ROLE == 1100 // adding of bits, granting multiple roles

AnyNumber & WALLETS_ADMIN_ROLE > 0 // checking if AnyNumber contains 0100 bit, it can be used for checking the role

The contract implements simple methods to manipulate Access Controls and check the roles. Note that granting new and revoking existing roles must be done in separate transactions.

 function grantRole(address addr, uint8 role) public validRole(role) validAddress(addr) onlyContractAdmin

function revokeRole(address addr, uint8 role) public validRole(role) validAddress(addr) onlyContractAdmin

function hasRole(address addr, uint8 role) public view validRole(role) validAddress(addr) returns (bool)

Appendix

Roles Matrix

Admin permissions are defined with the following binary values. Roles are defined as the OR of a set of admin roles:

Contract Admin: 0001 Reserve Admin: 0010 Wallets Admin: 0100 Transfer Admin: 1000

Role IntegerAdmin RolesBit Mask Representation
0None (Default)0000
1Contract Admin0001
2Reserve Admin0010
3Contract Admin + Reserve Admin0011
4Wallets Admin0100
5Contract Admin + Wallets Admin0101
6Reserve Admin + Wallets Admin0110
7Contract Admin + Reserve Admin + Wallets Admin0111
8Transfer Admin1000
9Contract Admin + Transfer Admin1001
10Reserve Admin + Transfer Admin1010
11Contract Admin + Reserve Admin + Transfer Admin1011
12Wallets Admin + Transfer Admin1100
13Contract Admin + Wallets Admin + Transfer Admin1101
14Reserve Admin + Wallets Admin + Transfer Admin1110
15All Roles (Contract, Reserve, Wallets, Transfer)1111

External Security

Because snapshots are used to calculate share of dividends awarded to recipients, proportional to token holdings, there is a risk of front-running from malicious holders.

It is therefore recommended to submit snapshot calls through private transaction pools (perhaps Flashbots or similar). This must be implemented at the application layer.