- 1 Summary
- 2 Issue Overview
- 3 Issue Details
- 3.1 SecurityToken contract should always be initialized
- 3.2 Unpredictable behavior due to front running or general bad timing
- 3.3 Polymath can arbitrarily change prices during a
USDTieredSTO
token sale - 3.4 No new ST can be created after implementation upgrade
- 3.5 ModuleRegistry - Custom modules can block their own removal
- 3.6 Transfer decisions by default should be consistent
- 3.7 ModuleRegistry - Custom module verification should be bounded to module version
- 3.8 ModuleRegistry - Risks of allowing custom modules in the system
- 3.9
_balanceOfByPartition
function returns wrong value - 3.10
_returnPartition
function of SecurityToken always returns UNLOCKED partition - 3.11
partitionsOf
function always returns empty array - 3.12 Security token upgrade info should be editable
- 3.13 VestingEscrowWallet - Integer Underflow and unchecked array access in
pushAvailableTokensMulti()
- 3.14 SecurityTokenRegistry does not inherit from
ISecurityTokenRegistry
- 3.15 Different implementations for the same modifier
whenNotPausedOrOwner
- 3.16 SecurityToken - security token name change may cause inconsistency
- 3.17 Where possible, a specific contract type should be used rather than
address
- 3.18 SecurityToken - Missing Input Validation
changeName
- 3.19
deleteDelegate
should be implemented without array iteration in permission manager - 3.20 Improve Code Reusability - Use const variables instead of literals for EIP-1066 status codes.
- 3.21 TokenLib - Unused Import
KindMath
- 3.22 Module - authentication modifier
onlyFactoryOwner
andonlyFactoryOrOwner
are never used - 3.23 SecurityToken - authentication modifier
onlyTokenFactory
is never used - 3.24 SecurityToken/STGetter (ERC1643) getAllDocuments - can be cause gas or memory issues if used
- 3.25 PolyToken - events are redefined in the implementation
- 3.26 Use latest stable version of Solidity
- 3.27
OwnedProxy
should be removed - 3.28
Module.takeUsageFee()
allows admins to drain a module's approved POLY tokens - 3.29 Redundant
pause
andunpause
functions inTransferManager
- 3.30 Modules shouldn't be casted to
IBoot
- 3.31
public
functions inTokenLib
could beexternal
instead - 3.32
EtherDividendCheckpoint
andERC20DividendCheckpoint
division by zero - 3.33
SecurityToken
/STGetter
storage layout is hard to maintain - 3.34
LockUpTransferManager._removeLockUpFromUser
checks for an impossible condition
- 4 Threat Model
- 5 Tool-Based Analysis
- 6 Test Coverage Measurement
- 7 Business Logic Review v2.2.0 to v3.0.0 Modules
- Appendix 1 - File Hashes
- Appendix 2 - Severity
- Appendix 3 - Disclosure
ConsenSys Diligence conducted a security audit on the Polymath Core smart contracts that comprise a system for launching regulatory-compliant securities tokens on a decentralized blockchain.
- Project Name: Polymath-Core
- Client Name: Polymath Inc.
- Client Contact: Adam Dossa
- Lead Auditor: Martin Ortner
- Co-auditors: Sergii Kravchenko, Steve Marx
- Date: 13 May 2019
- Commit Hash: #7a70aa5 (tag/3.0.0-audit)
- Repository: https://github.com/PolymathNetwork/polymath-core/releases/tag/3.0.0-audit
Minor | Medium | Major | Critical | |
---|---|---|---|---|
Open | 0 | 0 | 0 | 0 |
Closed | 20 | 3 | 7 | 4 |
The focus of the audit was to verify that the smart contract system is secure, resilient and working according to its specifications. The audit activities can be grouped in the following three categories:
Security: Identifying security related issues within each contract and within the system of contracts.
Sound Architecture: Evaluation of the architecture of this system through the lens of established smart contract best practices and general software best practices.
Code Correctness and Quality: A full review of the contract source code. The primary areas of focus include:
- Correctness
- Readability
- Sections of code with high complexity
- Improving scalability
- Quantity and quality of test coverage
The following documentation was available to the audit team:
This section outlines the top-level contracts and libraries of the system. A summary of the inheritance structure for the main component groups is given in the Design section. A complete view of the inheritance structure can be found here (dot). The complete call-graph is too large to be rendered as an image and is therefore only provided as a graphviz dot file.
- PolymathRegistry
- FeatureRegistry
- ModuleRegistry and ModuleRegistryProxy
- SecurityTokenRegistry, STRGetter and SecurityTokenRegistryProxy
- MakerDAOOracle
- StableOracle
- PolyOracle
- SecurityTokenFactory (STFactory)
- SecurityToken, STGetter and SecurityTokenProxy
- ERC20DividendCheckpoint, -Factory and -Proxy
- EtherDividendCheckpoint, -Factory and -Proxy
- WeightedVoteCheckpoint, -Factory and -Proxy
- PLCRVotingCheckpoint, -Factory and -Proxy
- CappedSTO, -Factory, -Storage and -Proxy
- USDTieredSTO, -Factory, -Storage and -Proxy
- PresaleSTO, -Factory, -Storage and -Proxy
- BlacklistTransferManager, -Factory, -Storage and -Proxy
- CountTransferManager, -Factory, -Storage and -Proxy
- GeneralTransferManager, -Factory, -Storage and -Proxy
- LockupTransferManager, -Factory, -Storage and -Proxy
- ManualApprovalTransferManager, -Factory, -Storage and -Proxy
- PercentageTransferManager, -Factory, -Storage and -Proxy
- VolumeRestrictionTransferManager, -Factory, -Storage and -Proxy
- VestingEscrowWallet, -Factory, -Storage and -Proxy
- GeneralPermissionManager, -Factory, -Storage and -Proxy
The scope of the audit was defined as the Solidity code in the ./contracts/
folder except
- Experimental modules (
contracts/modules/Experimental
) - Migrations
- Mocks (
contracts/mocks
) - External (
contracts/external
) - Helpers (
contracts/helpers
)
The list of files and their hashes can be found in Appendix 1 - File Hashes.
In addition to the v3.0.0 audit the team also reviewed the three v2.2.0 modules to match their v3.0.0 counterparts. Details can be found in the section Business Logic Review v2.2.0 to v3.0.0 Modules.
The system is a framework to launch and manage security tokens. An end user typically creates a new security token by registering a token symbol and deploying a security token contract. A security token can make use of one or more modules that specify the behavior of the token.
The system consists of three major component groups:
-
Registries
(dot)
Registries are administered and deployed by Polymath and form the backbone of the system. There are four Registries:
-
SecurityTokenRegistry
The
SecurityTokenRegistry
is the main entry point to the protocol and allows token issuers to register symbols and generate new security tokens. It keeps track of all security tokens and links issuers to security tokens and the symbols they reserved.The
SecurityTokenRegistry
is proxied and can be upgraded. Ownership remains with Polymath.Main Functionality:
- manage symbols: create, modify, and remove tickers and transfer their ownership
- manage security tokens: generate and modify security tokens
- manage fees: handle fees for ticker registration and security token launch
-
ModuleRegistry
The
ModuleRegistry
keeps track of all registered modules that are available to security tokens and provides access to the module factories.The
ModuleRegistry
is proxied and can be upgraded. Ownership remains with Polymath.Main Functionality:
- manage modules: register, remove, verify, unverify, and access modules
-
FeatureRegistry
The
FeatureRegistry
supports enabling and disabling features in the system. It maps a feature name to a boolean value indicating whether that feature is enabled. In the audited code, the only feature tracked is calledcustomModulesAllowed
. This feature is checked by theModuleRegistry
to see whether custom modules (those not managed by Polymath) should be allowed.The
FeatureRegistry
is not proxied and cannot be upgraded. Ownership remains with Polymath.Main Functionality:
- manage features: globally enable and disable features
-
PolymathRegistry
The
PolymathRegistry
is the main registry that keeps track of core components of the system including the addresses of thePolyToken
(e.g. used in the fee system), theModuleRegistry
, theFeatureRegistry
, and theSecurityTokenRegistry
.The
PolymathRegistry
is not proxied and cannot be upgraded. Ownership remains with Polymath.Main Functionality:
- manage core components: set and change the address of core components.
(dot)
-
-
SecurityToken
(dot)
This is the central component of the system which implements ERC 1400, also known as the Security Token Standard, which is comprised of a group of standards that aim to create a representation of securities on the Ethereum blockchain. It is compatible with ERC 20 and ERC 777 and includes:
- ERC 1410 - Partially Fungible Token Standard
- ERC 1594 - Core Security Token Standard
- ERC 1643 - Document Management Standard
- ERC 1644 - Controller Token Operation Standard
Security Tokens can have modules attached to them. Modules control the Security Tokens behavior and might have special capabilities or permissions in the system.
To issue a new
SecurityToken
an issuer interfaces with theSecurityTokenRegistry
to register a symbol and generate a new security token. TheSecurityTokenRegistry
defines which factory (STFactory
) is used to deploy a newSecurityTokenProxy
linked to a specific implementation of theSecurityToken
. TheSecurityTokenFactory
by default adds a data store and default transfer manager module (unless no defaults are set) and sets the treasury wallet address for the security token. The issuer may then attach other modules to the security token.The
SecurityToken
is proxied and can be upgraded.
Ownership of the SecurityTokenFactory (STFactory
) and logic contracts (SecurityToken
implementation) remains with Polymath. Ownership of the security token (SecurityTokenProxy
) is finally transferred to theissuer
.Main Functionality:
- manage modules: add, remove, archive, unarchive, and upgrade modules
- manage the security token: update token details and name, upgrade the token
- manage security token transfers: transfer, freeze, and unfreeze transfers
(dot)
-
Modules
Modules control the security token's behavior and can have special capabilities or permissions in the system.
The system comes with five types of modules:
-
TransferManager
(dot)
A transfer manager controls the transfer of tokens. The following variants of transfer managers are provided by Polymath:
- BlacklistTransferManager - prevent certain investors from transferring tokens during issuer-defined blackout periods
- CountTransferManager - only allow up to a specified number of token holders
- GeneralTransferManager - enforces whitelists, KYC, and other restrictions
- LockupTransferManager - specify named lockup periods for certain investors
- ManualApprovalTransferManager - transfers can only be made if pre-approved by token admin
- PercentageTransferManager - limit the percentage of total tokens that can be owned by a single account
- VolumeRestrictedTransferManager - limit the number of tokens that can be owned by a single account
(dot)
-
PermissionManager
(dot)
The follow variants of permission managers are provided by Polymath:
- GeneralPermissionManager - allows the issuer to add wallets as delegates and gives permissions to use restricted functions on other modules
-
CheckPoint
(dot)
CheckPoint modules keep track of and make use of historical token balances.
-
DividendCheckpoint Modules
Creates checkpoint when dividends are paid so token holders are paid proportionally according to the token balance they held at the time the dividend was created.
The system provides two variants of DividendCheckpoint modules: An
ERC20DividendCheckpoint
and anEtherDividendCheckpoint.
-
VotingCheckpoint Modules
Allow the token owner to create ballots for voting, where voting weights are determined by the token balances of each voter at the time the vote was created.
-
WeightedVoteCheckpoint
Basic weighted vote checkpoint module.
-
PLCRVotingCheckpoint
The partial-lock commit/reveal (PLCR) vote uses a commit/reveal scheme to keep votes private during the voting period. Votes are revealed when voting has completed.
-
(dot)
-
-
SecurityTokenOffering
(dot)
SecurityTokenOffering modules allow security tokens to be issued in return for investment in various currencies (ETH, POLY and USD stable coin)
-
PresalesSTO - private presales
-
USDTieredSTO - price of token is specified in US dollars, with multiple price tiers
-
CappedSTO - sets a limit on the funding that is accepted and tokens that will be distributed
(dot)
-
-
Wallet
(dot)
The system provides a
VestingEscrowWallet
by default that allows approved staff to create a token vesting schedule for employees/affiliates.(dot)
-
-
Oracles
(dot)
Oracles are a trusted entity in the system. Polymath wraps the oracles in order to allow a manual price override or to freeze the price. For example, oracles are used with STO modules to convert USD prices into POLY or ether.
(dot)
- Best practices are followed where they make sense.
- Common design patterns are used where required instead of inventing new interfaces and designs.
- The token is following the ERC1400 Security Token Standard.
- An extensive test-suite with 1544 individual tests is provided with the framework.
- Arithmetic operations are performed using
SafeMath
. - Smart contracts are split into multiple files, most of them being comprised of an interface and an implementation. Common functionality is provided by library contracts.
- Inline documentation for methods and classes is available and describes functionality and input parameters.
- The codebase did not yield compiler warnings that were in scope for this audit.
- The project provided easy to use scripts (
npm
/package.json
) for flattening relevant contracts, executing the test-suite and generating coverage statistics.
- Test coverage is incomplete. Any contract system that is used on the main net should have as a minimum requirement a 100% test coverage especially with a risk profile of a Security Token Network. In particular it's useful to include negative test cases ensuring that undesirable changes are detected early and kept in-line with the specification. Security focused test cases verifying that permissions are set-up according to the security-specification and trust-model should be consistently implemented. Methods that require special permissions must be tightly covered. None of the contracts that comprise the contract system should be exempt from testing/coverage. Please not that having a 5% rate of uncovered statements can easily mean that a couple of hundred statements are uncovered for large projects, potentially including critical code paths as well.
- High Complexity. A multiple library/contract system is complex in nature. The Polymath system consists of a high number of individual contracts that need to be deployed and maintained. The inter-contract communication rate is high. The inheritance model of the system is complex.
- The projects
npm run flatten-all
script does not flatten all relevant modules. - Improve the Documentation. Inconsistencies exist between the specification documents provided during the audit as well as the projects wiki.
It is recommended to fix all the issues listed in the following chapters, at the very least the ones with severity Critical, Major and Medium.
Individual issues are listed below, but it's important to note that the contract system is quite large, with complex proxying, upgradeability mechanisms, exponential combinations of modules, and insufficient testing. This means that there are likely vulnerabilities in the system that our team did not find. In addition to existing hidden vulnerabilities, new modules or upgrades to existing components could easily create new vulnerabilities. Fortunately, the administrative control retained by Polymath and token issuers will make it possible to recover from many types of vulnerabilities once discovered.
The following table contains all the issues discovered during the audit. The issues are ordered based on their severity. More detailed description on the levels of severity can be found in the Severity Definitions Appendix. The table also contains the status of any discovered issue.
Severity: Critical
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#672.
NB - we also fixed this in the SecurityTokenRegistry which had a similar issue.
All security tokens are proxies that delegate all calls to SecurityToken implementation contract deployed by the Polymath. Since SecurityToken is a proxy itself, it's possible to delete the SecurityToken contract if it delegates calls to a malicious contract with selfdestruct
. Because of that, it might be possible to use Create2 to replace the implementation with a malicious contract and steal tokens. Create2 attack can only be done by polymath, but anyone else can just delete the SecurityToken and break all the tokens irrevocably.
Ensure initialization of the SecurityToken implementation contract with the safe getterDelegate
. That prevents from creating an opportunity to destroy the contract or to use it maliciously.
For example, it should be enough to add initialized = true;
to SecurityToken constructor.
Severity: Critical
Status: Closed
Resolution: This issue has been addressed with the following comment:
PolymathRegistry /
SecurityToken.updateFromRegistry
: We have left this alone. In practice we don't expect issuers to need to call this (as all registries can be updated in place) and if they do, they would trigger it, not us.New modules: We have left the logic as it is. If an issuer is concerned about this risk they can add the new module with
_archived
set to true which would add the module but not activate it. They can then check the details of the module added before activating it. They can also specify an upper bound on the module fee to avoid any front-running to change the fee.New tokens: We have modified
setProtocolFactory()
to not allow changing the factory associated with an existing version. An issuer can now specify an exact version to deploy and we can't modify that version by front-running their transaction: PolymathNetwork/polymath-core#689In general we will use the CD audit report to generate a transparency document specifying the areas that Polymath has control in, and the areas that we don't. The guiding principle is that, until the point that a token is created, Polymath has a good amount of control (e.g. through front-running an upgrade to an STFactory token) but once the token has been deployed we have no unilateral control (with certain documented exceptions, specifically that we can render the module registry inactive and have some initial access over the oracle pricing for STOs (see 3.3)).
In a large number of cases, Polymath can update or upgrade things in the system without warning. This has the potential to violate a security goal of the system: once a security token has been created, Polymath shouldn't be able to interfere with that token's functioning.
Specifically, Polymath could use front running to make malicious changes just ahead of incoming transactions, or purely accidental negative effects could occur due to unfortunate timing of changes.
Some instances of this are more important than others, but in general token issuers should have assurances about the behavior of the action they're about to take.
The PolymathRegistry
is used to store addresses for a wide variety of things, including oracles, the POLY token, security token registry, module registry, etc. By front running a call to SecurityToken.updateFromRegistry
, Polymath could get a security token to use arbitrary addresses for the module registry, security token registry, and POLY token. In general, any call to PolymathRegistry.getAddress
is vulnerable to front running.
When adding a new module to a token, the latest version is used from the module token registry. By front running a call to add a module to an existing security token, Polymath could add a new version of a module and cause the new code to be added to the existing security token. If the module is of the right type, e.g. one that allows minting and/or burning tokens, this could allow for direct theft of valuable assets.
Similarly, when deploying a new security token, the latest version is used from the security token registry. Someone creating a token thus has no way to be sure what version of the code they'll receive when they deploy a new token.
The underlying issue is that users of the system can't be sure what the behavior of a function call will be, and this is because the behavior can change at any time.
There are two broad strategies for addressing this:
- Let the user lock things down from changing. For example, allow them to specify what version of a module or token contract they expect, and if that version is no longer current by the time the transaction is processed, revert.
- Give the user advance notice of changes with a time lock. For example, make all upgrades require two steps with a mandatory time window between them. The first step merely broadcasts to users that a particular change is coming, and the second step commits that change after a suitable waiting period.
Different aspects of the system may call for different specific remediations.
See also issue 3.3, which discusses the danger of oracle manipulation in particular.
Severity: Critical
Status: Closed
Resolution: This issue has been addressed with PolymathNetwork/polymath-core#691.
We have added a function
modifyOracle
that allows an issuer to take control of their own oracles if they wish to. The default is to use the Polymath "controlled" oracles. This follows the pattern of defaulting to have a certain amount of trust in Polymath but allowing an issuer to remove this if required.
The USDTieredSTO
token sale uses oracles to convert from USD to ETH or POLY. The oracle addresses come from the PolymathRegistry
, which means that Polymath has the ability to swap out those oracles at any time. This enables Polymath to set an arbitrary price for themselves to participate in an STO, including a price of 0
.
We recommend putting time locks in place. It shouldn't be possible to replace the oracle implementations or to put them into manual override mode without announcing the change first and waiting a reasonable amount of time. This gives token issuers an opportunity to shut down any ongoing token sales before the oracle changes take effect.
Severity: Critical
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#694 (also see issue 3.12)
Polymath should be able to upgrade SecurityToken implementation by creating a new security token contract. After that, a link to the new version should be set in STFactory contract by calling setLogicContract
function.
code/contracts/tokens/STFactory.sol:L139-L148
function setLogicContract(string calldata _version, address _logicContract, bytes calldata _upgradeData) external onlyOwner {
require(keccak256(abi.encodePacked(_version)) != keccak256(abi.encodePacked(logicContracts[latestUpgrade].version)), "Same version");
require(_logicContract != logicContracts[latestUpgrade].logicContract, "Same version");
require(_logicContract != address(0), "Invalid address");
latestUpgrade++;
logicContracts[latestUpgrade].version = _version;
logicContracts[latestUpgrade].logicContract = _logicContract;
logicContracts[latestUpgrade].upgradeData = _upgradeData;
emit LogicContractSet(_version, _logicContract, _upgradeData);
}
The problem is that in this function initializationData
is not set for the latest version of the logic contract. That means that all new security tokens will not have init data and they will not be initialized and most probably will revert on creation.
Add logicContracts[latestUpgrade].initializationData
initialisation in setLogicContract
and check that initializationData
length is at least 4 bytes.
Severity: Major
Status: Closed
Resolution: closed with PolymathNetwork/polymath-core#698
The module registry is controlled by the module registry owner. Both the registry owner and the module owner may remove modules from the registry. Modules are usually deployed by Polymath but the system also allows the use of custom modules. Custom modules can be enabled in the feature registry. This allows potentially untrusted entities to register modules while remaining in control of the module factory.
code/contracts/ModuleRegistry.sol:L207-L214
function removeModule(address _moduleFactory) external whenNotPausedOrOwner {
uint256 moduleType = getUintValue(Encoder.getKey("registry", _moduleFactory));
require(moduleType != 0, "Module factory should be registered");
require(
msg.sender == IOwnable(_moduleFactory).owner() || msg.sender == owner(),
"msg.sender must be the Module Factory owner or registry curator"
);
The order of the ownership checks in the current implementation give precedence to a potentially untrusted external call to the module factory to check if the sender is the module owner. Modules can therefore intentionally block their own removal by reverting on the external ownership call (IOwnable(_moduleFactory).owner()
) . The check if the module registry owner is the sender will not be performed.
Make sure the module registry owner can always remove modules from the registry. Avoid external calls to untrusted modules that might intentionally revert the execution. Give precedence to the trusted call and only after that call out to untrusted contracts.
Please note that unverifyModule()
can also be blocked in the same way by a malicious module.
- also see issue 3.8
Severity: Major
Status: Closed
Resolution: Logic has been modified so that transfers fail if there are no unarchived TMs. Documentation around the behaviour has been added: PolymathNetwork/polymath-core#693 https://github.com/PolymathNetwork/polymath-core/wiki/Transfer-manager-results
code/contracts/tokens/SecurityToken.sol:L661-L696
function _executeTransfer(
address _from,
address _to,
uint256 _value,
bytes memory _data
)
internal
checkGranularity(_value)
returns(bool)
{
if (!transfersFrozen) {
bool isInvalid;
bool isValid;
bool isForceValid;
bool unarchived;
address module;
uint256 tmLength = modules[TRANSFER_KEY].length;
for (uint256 i = 0; i < tmLength; i++) {
module = modules[TRANSFER_KEY][i];
if (!modulesToData[module].isArchived) {
unarchived = true;
ITransferManager.Result valid = ITransferManager(module).executeTransfer(_from, _to, _value, _data);
if (valid == ITransferManager.Result.INVALID) {
isInvalid = true;
} else if (valid == ITransferManager.Result.VALID) {
isValid = true;
} else if (valid == ITransferManager.Result.FORCE_VALID) {
isForceValid = true;
}
}
}
// If no unarchived modules, return true by default
return unarchived ? (isForceValid ? true : (isInvalid ? false : isValid)) : true;
}
return false;
}
If there are no active modules then _executeTransfer
returns true
by default. If there are active modules and all modules say NA
transfer should have the same result, but it returns false
.
Make default decisions consistent. It looks like it makes more sense to make all transfers VALID by default.
Severity: Major
Status: Closed
Resolution: This issue has been addressed as part of issue 3.8. Module verification is not bound to version.
If custom modules feature is enabled there is a mechanism of registering new custom modules by anyone. These modules can only be used by any other security token if polymath admins verify them. The problem is that verification is only bounded to module factory regardless of module version.
code/contracts/ModuleRegistry.sol:L245
function verifyModule(address _moduleFactory) external onlyOwner {
Once the factory is verified, the factory owner can upgrade a module to a malicious one.
Add a module version to verification details and only allow a verified version to be used in security tokens. That would require polymath admins to review every new upgrade in a custom module. As an alternative, polymath admins can just make sure that they only verify module factories that are not upgradable.
Severity: Major
Status: Closed
Resolution: The functionality to register custom modules to the system remains available, increases complexity and extends the attack surface. A number of risks described with this issue have been addressed with PolymathNetwork/polymath-core#698.
- made
useModule
andregisterModule
non-reentrant as they make external calls to potentially untrusted contracts- store factory owner on initial registration locally and reference this rather than calling back out to owner on the module factory. This means that a malicious factory cannot change the value of owner dynamically to e.g. allow security tokens to add it (by returning an owner equal the ST owner). The initial owner cannot be malicious as
registerModule
requiresmsg.sender
to be the factory owner.- fix issue where a malicious factory could return a type of 0, causing it to be un-removable
- modified
getModulesByType
to only return verified modules, so that a single malicious unverified module cannot grief this function.- added
getAllModulesByType
can be used to return a raw list of all registered modules instead- modify
getFactoryDetails
to also return the factory owner
This leaves the issue that someone could register ~8k new modules and cause loading
moduleList
to exceed the block gas limit. Whilst this is theoretically possible, in practice it would cost ~120 ETH and if it were to happen we could simply upgrade the ModuleRegistry to fix the problem. This should be a sufficient disincentive for anyone to attempt this griefing attack.
The global setting customModulesAllowed
is maintained in the FeatureRegistry
and controls whether it is allowed to register custom modules in the ModuleRegistry
for use with security tokens. By default only the ModuleRegistry
owner can register modules. Setting customModulesAllowed
opens up registration to anyone for custom modules. This has several security implications.
There are two scenarios in connection with custom modules:
-
A security token issuer would like to use their custom modules with their own security token (common use-case).
-
A module developer would like to make their module generally available to other users in the system (marketplace use-case). This requires the module to be verified by Polymath.
code/contracts/ModuleRegistry.sol:L171-L180
function registerModule(address _moduleFactory) external whenNotPausedOrOwner {
if (IFeatureRegistry(getAddressValue(FEATURE_REGISTRY)).getFeatureStatus("customModulesAllowed")) {
require(
msg.sender == IOwnable(_moduleFactory).owner() || msg.sender == owner(),
"msg.sender must be the Module Factory owner or registry curator"
);
} else {
require(msg.sender == owner(), "Only owner allowed to register modules");
}
require(getUintValue(Encoder.getKey("registry", _moduleFactory)) == 0, "Module factory should not be pre-registered");
- Anyone (usually a security token owner) can register a
moduleFactory
by callingregisterModule()
provided the caller is the owner of the factory. - After getting the factories
moduleType
, the factory is stored in themoduleList
. The module is still unverified and can only be used by a security token owner that owns the factory. This gives anyone control over the size of themoduleList
allowing someone to spam the list and potentially cause DoS conditions in other parts of the system that are iterating over all items in themoduleList
(see subsection Listing modules) - Note: the method is inefficient as it performs multiple external calls instead of re-using previously returned values (
moduleFactory.types()
,IOwnable(_moduleFactory).owner()
).
code/contracts/ModuleRegistry.sol:L132-L140
function useModule(address _moduleFactory, bool _isUpgrade) external {
if (IFeatureRegistry(getAddressValue(FEATURE_REGISTRY)).getFeatureStatus("customModulesAllowed")) {
require(
getBoolValue(Encoder.getKey("verified", _moduleFactory)) || IOwnable(_moduleFactory).owner() == IOwnable(msg.sender).owner(),
"ModuleFactory must be verified or SecurityToken owner must be ModuleFactory owner"
);
} else {
require(getBoolValue(Encoder.getKey("verified", _moduleFactory)), "ModuleFactory must be verified");
}
code/contracts/ModuleRegistry.sol:L345-L371
function getModulesByTypeAndToken(uint8 _moduleType, address _securityToken) public view returns(address[] memory) {
address[] memory _addressList = getArrayAddress(Encoder.getKey("moduleList", uint256(_moduleType)));
uint256 _len = _addressList.length;
bool _isCustomModuleAllowed = IFeatureRegistry(getAddressValue(FEATURE_REGISTRY)).getFeatureStatus(
"customModulesAllowed"
);
uint256 counter = 0;
for (uint256 i = 0; i < _len; i++) {
if (_isCustomModuleAllowed) {
if (IOwnable(_addressList[i]).owner() == IOwnable(_securityToken).owner() || getBoolValue(
Encoder.getKey("verified", _addressList[i])
)) if (isCompatibleModule(_addressList[i], _securityToken)) counter++;
} else if (getBoolValue(Encoder.getKey("verified", _addressList[i]))) {
if (isCompatibleModule(_addressList[i], _securityToken)) counter++;
}
}
address[] memory _tempArray = new address[](counter);
counter = 0;
for (uint256 j = 0; j < _len; j++) {
if (_isCustomModuleAllowed) {
if (IOwnable(_addressList[j]).owner() == IOwnable(_securityToken).owner() || getBoolValue(
Encoder.getKey("verified", _addressList[j])
)) {
if (isCompatibleModule(_addressList[j], _securityToken)) {
_tempArray[counter] = _addressList[j];
counter++;
}
- Modules are organized in typed
moduleLists
and custom modules are not separated from built-in modules. This opens the possibility for a malicious actor to cause a DoS condition by filling themoduleList
with dummy factories resulting in an out of gas condition while iterating the list. - Even more straight forward a malicious module could just revert in the call to
IOwnable(_addressList[i]).owner()
causing the method to always fail. - The check whether a module is verified should be performed first to allow a shortcut that is avoiding the external call checking whether the module factory is owned by the security token owner. Note that the external call may intentionally fail (issue 3.5).
code/contracts/ModuleRegistry.sol:L354-L355
if (IOwnable(_addressList[i]).owner() == IOwnable(_securityToken).owner() || getBoolValue(
Encoder.getKey("verified", _addressList[i])
There is considerable risk in allowing untrusted parties to provide and maintain custom modules in the system. Untrusted modules can be registered by anyone, are not separated from built-in modules and may therefore interfere with the system by causing Out-of-Gas DoS conditions, unexpected reverts or open up a potential for reentrancy attacks.
It is therefore recommended to assess whether this functionality is actually being used or required in the system. The general advice is to remove any functionality that is not actively used or still in development. To safely allow the use of custom modules it is recommended to rework the current code based on the changing trust assumptions.
- A custom module can only be used if the
moduleFactory
owner is the security token owner, or themoduleFactory
is verified by Polymath. In order to get a module verified a manual step is required. In any case this step must include a thorough security inspection of the module to make sure that themoduleFactory
and module implementations adhere to the interfaces provided by Polymath and to verify that themoduleFactory
has no malicious intent, i.e. none of the module components can be updated after the verification has been performed. - Trust assumptions are changing due to the external party maintaining full control of the module and its components (factory, implementation).
- A verification should always be bound to a specific version of a module (issue 3.7)
- Custom modules should be separated from built-in modules and the code should be refactored to make sure the existence of custom modules does not interfere with users relying on built-in modules (e.g. DoS/Out-of-Gas conditions).
- Always make sure that the registry owner is able to maintain and administer the registry and cannot be blocked by an external call (issue 3.5)
- Guard for potential reentrancy attacks.
Severity: Major
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#679.
// In UNLOCKED partition we are returning the minimum of all the unlocked balances // In locked partition we are returning the maximum of all the Locked balances
code/contracts/tokens/SecurityToken.sol:L490
function _balanceOfByPartition(bytes32 _partition, address _tokenHolder, uint256 _additionalBalance) internal view returns(uint256 max) {
This function returns the maximum of the balances from all transfer managers. It should not be the case for UNLOCKED partition for example, because GTM may return all balance, while LTM can have half of the balance locked. In the result _balanceOfByPartition(UNLOCKED)
will return full balance, but only half of it can actually be transferred.
UNLOCKED
partition balance should be the minimum of all balances from all transfer managers.
There is an issue with that solution because some transfer managers do not implement this function properly and just return 0. That needs to be fixed by implementing getTokensByPartition
function in every TM or adding a flag that shows whether this function is implemented.
LOCKED
partition balances should remain the same (maximum).
Severity: Major
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#680
code/contracts/tokens/SecurityToken.sol:L538-L545
function _returnPartition(uint256 _beforeBalance, uint256 _afterBalance, uint256 _value) internal pure returns(bytes32 toPartition) {
// return LOCKED only when the transaction `_value` should be equal to the change in the LOCKED partition
// balance otherwise return UNLOCKED
if (_afterBalance.sub(_beforeBalance) == _value)
toPartition = LOCKED;
// Returning the same partition UNLOCKED
toPartition = UNLOCKED;
}
It's commented in the function that it should return LOCKED
under some circumstances.
Function _returnPartition
always returns UNLOCKED
partition.
Additionally, the comment says that LOCKED
should be returned when a change in the locked partition equals value
but it actually checks the change in _partition
which is usually UNLOCKED
Review what actually should be returned here. If the initial intention is correct, actually check LOCKED
partition balance change and return correct partition.
Severity: Major
Status: Closed
Resolution: addressed with PolymathNetwork/polymath-core#681 by hard-coding the partitions that are supported in 3.0.
partitionsOf
is a function inside STGetter
contract that should return a list of the partitions for a particular token holder. _appendPartition
function is used inside partitionsOf
to append a new partition to the partitions array in case it's not already there. Instead, it does the opposite and only appends partitions that are already in the partitions array. Eventually, that results in the fact that no partitions are added and partitionsOf
always returns an empty array.
code/contracts/tokens/STGetter.sol:L279
if (duplicate) {
Change duplicate
to !duplicate
in if
statement.
Severity: Medium
Status: Closed
Resolution: This issue has been addressed with PolymathNetwork/polymath-core#694 (see issue 3.4) by adding functionality to edit upgrade info. Front-running opportunity remains.
Polymath admins can upgrade security token implementation. In order to do so, they submit an address of a new token implementation and upgradeData
for initialization. If they do it wrong, there is no way to edit that data and all tokens of lower versions will become non-upgradable.
Allow editing security token upgrade information. It's important to avoid adding frontrunning opportunities while doing that.
3.13 VestingEscrowWallet - Integer Underflow and unchecked array access in pushAvailableTokensMulti()
Severity: Medium
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#673.
pushAvailableTokensMulti()
attempts to check if the argument _toIndex
is within bounds of beneficiaries[]
. However, the check already underflows at line 436 if beneficiaries
is empty allowing the caller (withPerm(OPERATOR)
) to provide any value for _toIndex
that is smaller or equal to UINT256_MAX
. The failed check may also lead to an invalid array access at line 438 causing an exception.
code/contracts/modules/Wallet/VestingEscrowWallet.sol:L435-L441
function pushAvailableTokensMulti(uint256 _fromIndex, uint256 _toIndex) public withPerm(OPERATOR) {
require(_toIndex <= beneficiaries.length - 1, "Array out of bound");
for (uint256 i = _fromIndex; i <= _toIndex; i++) {
if (schedules[beneficiaries[i]].length !=0)
pushAvailableTokens(beneficiaries[i]);
}
}
A check for _toIndex < beneficiaries.length
is sufficient. Optionally also add a check to require _fromIndex <= toIndex
to avoid that the method silently returns without performing an action on invalid input.
Severity: Medium
Status: Closed
Resolution: This issue has been addressed with PolymathNetwork/polymath-core#682 updating the mismatching interface but not inheriting from it (due to some functions living in the proxied STRGetter). Recommendation: Add dev-note to SecurityTokenRegistry reminding dev to update interface as it is not automatically enforced.
We did not inherit ISTR from STR as this doesn't quite work as some of the functions in ISTR live in the STRGetter (due to contract size limitations). We did review and align the functions between these contracts though in: PolymathNetwork/polymath-core#682.
SecurityTokenRegistry.sol
imports ./interfaces/ISecurityTokenRegistry.sol
but it is not used in the code.
code/contracts/SecurityTokenRegistry.sol:L7
import "./interfaces/ISecurityTokenRegistry.sol";
SecurityTokenRegistry
should inherit from ISecurityTokenRegistry
to make sure it is an implementation compliant to the interface.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#674.
ModuleRegistry
and SecurityTokenRegistry
provide different implementations for the same modifier.
code/contracts/ModuleRegistry.sol:L82-L88
modifier whenNotPausedOrOwner() {
if (msg.sender == owner()) _;
else {
require(!isPaused(), "Already paused");
_;
}
}
code/contracts/SecurityTokenRegistry.sol:L168-L176
modifier whenNotPausedOrOwner() {
_whenNotPausedOrOwner();
_;
}
function _whenNotPausedOrOwner() internal view {
if (msg.sender != owner())
require(!isPaused(), "Paused");
}
Stick to one variant of the implementation to improve code maintainability.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#706.
We modified the code so that token name is only stored on the ST itself, and the STR retrieves the details from the ST directly.
To retain backwards compatibility, if there is a name stored in the STR it is returned.
When generating registering a ticker and generating a security token a name for the security token is specified. The token name is stored in the security token registry while registering the ticker name (_storeTickerDetails()
, registeredTickers_tokenName<TokenName>
). However, an issuer can unilaterally change the name of the token stored in the security token (see related issue issue 3.18). This update is not reflected in the security token registry causing an inconsistency with the security token.
code/contracts/SecurityTokenRegistry.sol:L462-L482
function _storeTickerDetails(
string memory _ticker,
address _owner,
uint256 _registrationDate,
uint256 _expiryDate,
string memory _tokenName,
bool _status
)
internal
{
bytes32 key = Encoder.getKey("registeredTickers_owner", _ticker);
set(key, _owner);
key = Encoder.getKey("registeredTickers_registrationDate", _ticker);
set(key, _registrationDate);
key = Encoder.getKey("registeredTickers_expiryDate", _ticker);
set(key, _expiryDate);
key = Encoder.getKey("registeredTickers_tokenName", _ticker);
set(key, _tokenName);
key = Encoder.getKey("registeredTickers_status", _ticker);
set(key, _status);
}
Indicate that the security token name stored in the registry is the initial token name or consider to disallow security token owners to change the name of their token.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#683.
Rather than storing address
es and then casting to the known contract type, it's better to use the best type available so the compiler can check for type safety.
ModuleStorage.securityToken
is of type address
, but it could be type ISecurityToken
instead. Not only would this give a little more type safety when deploying new modules, but it would avoid repeated casts throughout the codebase of the form ISecurityToken(securityToken)
.
Where possible, use more specific types instead of address
. This goes for parameter types as well as state variable types.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#695.
The security token name is initially set and validated when calling SecurityTokenRegistry.generateSecurityToken()
. After the new security token proxy has been deployed ownership is transferred to the issuer. The issuer can then call changeName
on the security token to set a new name without enforcing any restrictions.
SecurityTokenRegistry initial deployment and input validation
code/contracts/SecurityTokenRegistry.sol:L564-L575
function generateNewSecurityToken(
string memory _name,
string memory _ticker,
string memory _tokenDetails,
bool _divisible,
address _treasuryWallet,
uint256 _protocolVersion
)
public
whenNotPausedOrOwner
{
require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Bad ticker");
SecurityToken - no input validation
code/contracts/tokens/SecurityToken.sol:L361-L365
function changeName(string calldata _name) external {
_onlyOwner();
emit UpdateTokenName(name, _name);
name = _name;
}
Make sure to enforce consistent input validation while deploying a new security token, but also when allowing an issuer to change certain variables.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#696.
The approach taken here was to leave the array iteration, but modify
deleteDelegate
so that it can be used to prune the list of delegates without iterating through the entire list.
Since delegates are set by the issuer, and a reasonable number of delegates is 1 - 5, this seems a reasonable fix to us.
deleteDelegate
function of GeneralPermissionManager
contract iterates over all delegates to find the right one. It might become impossible to delete a delegate because of the array size (though it's unlikely, it still becomes more expensive). There should be a more efficient way to remove a delegate.
Store indexes of delegates in the array or have a view function to determine the array index and pass that index to the deleteDelegate
.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#690. Severity rating has been downgraded to Minor.
EIP-1066 Status code literals are used throughout the codebase which makes it hard to maintain and understand the codebase.
code/contracts/libraries/TokenLib.sol:L458-L483
function canTransfer(
bool success,
bytes32 appCode,
address to,
uint256 value,
uint256 balanceOfFrom
)
public
pure
returns (bool, byte, bytes32)
{
if (!success)
return (false, 0x50, appCode);
if (balanceOfFrom < value)
return (false, 0x52, bytes32(0));
if (to == address(0))
return (false, 0x57, bytes32(0));
// Balance overflow can never happen due to totalsupply being a uint256 as well
// else if (!KindMath.checkAdd(balanceOf(_to), _value))
// return (false, 0x50, bytes32(0));
return (true, 0x51, bytes32(0));
}
Consider creating a Library/SubContract that exports const EIP-1066 Status Codes with descriptive variable names for use in the codebase.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#684.
TokenLib
imports ./KindMath.sol
but it is never used.
code/contracts/libraries/TokenLib.sol:L10
import "./KindMath.sol";
Remove unused imports.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#685.
The modifiers onlyFactoryOwner
and onlyFactoryOrOwner
are defined in Module
but never used. This can indicate a lack of authentication control for methods exposed by any of the modules inheriting from Module
but can also be a preparation for future modules. The modifiers check if msg.sender
is the modules factory owner or msg.sender
is the factory calling the method. The modules factory is set in the constructor of ModuleStorage
when initializing the module.
code/contracts/modules/Module.sol:L48-L56
modifier onlyFactoryOwner() {
require(msg.sender == Ownable(factory).owner(), "Sender is not factory owner");
_;
}
modifier onlyFactoryOrOwner() {
require((msg.sender == Ownable(securityToken).owner()) || (msg.sender == factory), "Sender is not factory or owner");
_;
}
code/contracts/storage/modules/ModuleStorage.sol:L28
factory = msg.sender;
A general recommendation is to remove unused functionality and re-introduce it when there is actually an implementation that is using it. This makes it easier to understand what the code is actually doing and does not pose a risk of someone assuming certain security checks are in place. Verify all exposed interfaces of contracts inheriting from Module
for missing authentication. Remove the modifier if the TokenFactory does not call to any of the modules and no special authentication is required.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#686.
The modifier onlyTokenFactory
is defined in SecurityToken
but never used. This can indicate a lack of authentication control for methods exposed by SecurityToken
. The modifier checks if msg.sender
is the tokenFactory
that has been set while initializing the contract.
code/contracts/tokens/SecurityToken.sol:L169-L172
modifier onlyTokenFactory() {
require(msg.sender == tokenFactory);
_;
}
A general recommendation is to remove unused functionality and re-introduce it when needed. This makes it easier to understand what the code is actually doing and does not pose a risk of someone assuming certain security checks are in place. Verify exposed interfaces of SecurityToken
for missing authentication. Remove the modifier if the TokenFactory does not call SecurityToken
and no special authentication is required.
Severity: Minor
Status: Closed
Resolution: This issue has been addressed with the following statement:
In general, for getter functions which are only expected to be called off-chain, and can therefore have a very high gas limit (beyond the block gas limit) we are comfortable having functions which potentially have high gas usage. We have this pattern in getters (intended only for a dApp) throughout our code.
The function getAllDocuments()
can be blocked if too many documents are added. Contracts interacting with it might run out of gas. This can also create problems with other code that relies or tries to read the result value of getAllDocuments()
.
Because the output is unlimited, this can cause out of gas, memory or execution problems.
This problem can be avoided if there is no code that interacts with this function or the number of documents will always be a low number.
Calling the getAllDocuments()
function has this gas impact with the specified number of documents:
- 200 documents will use ~100k gas;
- 1000 documents will use ~370k gas;
- 1700 documents will use ~650k gas;
- 5000 documents will use ~2 million gas.
This contract was used to generate the gas costs above:
contract Reader {
ERC1643 public d;
constructor(ERC1643 _addr) public {
d = _addr;
}
uint256 public count;
function readDocs() public returns (uint256) {
count = d.getAllDocuments().length;
return count;
}
}
Because this is part of the standard implementation and the specs, there is no code remediation possible. The advice is to avoid calling the function and only use getDocument()
.
- Geth provides
MaxUint64 / 2
gas when callingview
, orconstant
functions but this was not always the case:
internal/ethapi/api.go:L740
gas := uint64(math.MaxUint64 / 2)
Ganache and Parity have different defaults. Other clients will implement their own defaults.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#692 by removing PolyToken
from the repository.
The PolyToken
implementation extends the IPoly
interface which defines the ERC20 events (Transfer
, Approval
) that can be emitted in the contract. Because the events are already defined in the interface, they do not need to be defined again in the implementation.
Defining the events again can lead to mistakes, in case the developer defines a similar but not quite exact event, or confusion, because somebody that reads the contract now needs to check if the specified event in the implementation matches the one defined in the interface.
Having the events defined only in one place reduces confusion and possible mistakes, while increasing code maintainability.
code/contracts/interfaces/IPoly.sol:L17-L18
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
code/contracts/helpers/PolyToken.sol:L80-L81
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
The code when emitting the Boom()
event in each case is very similar but the emitted event might not be the expected one.
pragma solidity >0.5.0;
interface MyInterface {
function explode(bytes calldata _data) external;
function implode(uint256 _data) external;
event Boom(bytes _data);
}
contract MyImplementation is MyInterface {
// We define an event with the same name as the one in the interface
// but a different argument type
event Boom(uint256 _data);
/*
This function emits the MyInterface.Boom(bytes _data) event:
[
{
"from": "0xec5bee2dbb67da8757091ad3d9526ba3ed2e2137",
"topic": "0xf0525cb4f38ada4b00a047310c294a112a153e48e11a55b7f4b2fe1024405383",
"event": "Boom",
"args": {
"0": "0x1234",
"_data": "0x1234",
"length": 1
}
}
]
*/
function explode(bytes memory _data) public {
emit Boom(_data);
}
/*
This function emits the MyImplementation.Boom(uint256 _data) event:
[
{
"from": "0xec5bee2dbb67da8757091ad3d9526ba3ed2e2137",
"topic": "0x1167d8fb28af59d807f1ba2dd442f6a78306131cc03e11970dd6e66c8adc9f57",
"event": "Boom",
"args": {
"0": "4660",
"_data": "4660",
"length": 1
}
}
]
*/
function implode(uint256 _data) public {
emit Boom(_data);
}
}
Remove the events which are already defined in the interface.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#675.
Solidity is very active in pushing out new versions. Make sure to include the latest stable version that is compatible with your code. The codebase is currently using solidity v0.5.0
.
code/flat/FeatureRegistry.sol:L1
pragma solidity ^0.5.0;
At the moment of writing, the latest stable version is 0.5.8.
It is a good practice to pin to a specific version. Having a floating version can open your application to newer, possibly not well tested, versions (SWC-103).
// Fixed version
pragma solidity 0.5.8;
// As opposed to a floating pragma
pragma solidity ^0.5.8;
https://github.com/ethereum/solidity/releases
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#701.
The OwnedProxy
contract is only used in the DataStoreProxy
, and Proxy
would be a better choice there. In general, there's no compelling use case for this contract, as ownership isn't being used for anything.
Have DataStoreProxy
inherit from Proxy
instead and then delete the dead OwnedProxy
code.
Severity: Minor
Status: Closed
Resolution: This issue has been closed by removing the functionality with PolymathNetwork/polymath-core#700.
There are no limits to when and how often Module.takeUsageFee()
can be called, and failure to collect has no on-chain consequences. It currently allows any admin to drain a module of approved POLY tokens. It, in fact, prevents modules from properly implementing POLY-based usage fees unless they take care to override this function.
Remove Module.takeUsageFee()
. If future modules want to collect a fee, they can implement a (safe) version of this function when it is needed.
Optionally, also remove ModuleFactory.usageCost
and ModuleFactory.usageCostInPoly()
, which will be completely unused if takeUsageFee()
is removed.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#676
code/contracts/modules/TransferManager/TransferManager.sol:L19-L27
function unpause() public {
_onlySecurityTokenOwner();
super._unpause();
}
function pause() public {
_onlySecurityTokenOwner();
super._pause();
}
pause
and unpause
functions implementations duplicate checks from Module
contract that TransferManager
inherits.
Remove pause
and unpause
functions from TransferManager
contract.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#687.
code/contracts/modules/ModuleFactory.sol:L222
bytes4 initFunction = IBoot(_module).getInitFunction();
Modules do not explicitly inherit IBoot
even though IModule
and IBoot
have the same function getInitFunction
.
Either cast _module
to IModule
or make IModule
inherit IBoot
.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#678.
Most, if not all, public
functions in TokenLib
could be made external
instead. This would save on gas at both deployment time and runtime. It would also make the code easier to read/audit by eliminating the possibility of internal callers.
Note that these functions could instead be made internal
, but this would cause them to be inlined into the calling contract, and we believe it was an intentional decision to use TokenLib
as a way to reduce the size of the calling contract.
Mark the functions as external
instead of public
wherever possible.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#697.
EtherDividendCheckpoint._createDividendWithCheckpointAndExclusions
and ERC20DividendCheckpoint._createDividendWithCheckpointAndExclusions
reject dividends when the currentSupply
is zero, presumably to prevent future division by zero errors when calculating dividend payouts. Code for EtherDividendCheckpoint
:
code/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpoint.sol:L134
require(currentSupply > 0, "Invalid supply");
However, the actual totalSupply
for the dividend is calculated by subtracting balances of excluded addresses from currentSupply
. This means that a zero is possible:
code/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpoint.sol:L152-L158
for (uint256 j = 0; j < _excluded.length; j++) {
require(_excluded[j] != address(0), "Invalid address");
require(!dividends[dividendIndex].dividendExcluded[_excluded[j]], "duped exclude address");
excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOfAt(_excluded[j], _checkpointId));
dividends[dividendIndex].dividendExcluded[_excluded[j]] = true;
}
dividends[dividendIndex].totalSupply = currentSupply.sub(excludedSupply);
(The same issue is present in ERC20DividendCheckpoint
.)
The impact of this bug is minor because no payouts will be possible on a dividend with no eligible balances.
The best fix is probably to move the require
statement down below the totalSupply
calculation and base it on that value:
require(dividends[dividendIndex].totalSupply > 0, "Invalid total supply");
Severity: Minor
Status: Closed
Resolution: This issue has been addressed with PolymathNetwork/polymath-core#704.
We added a script to our deployment pipeline to check that memory is laid out correctly.
SecurityToken
contract delegates to STGetter
as a workaround for contract deployment size limitations. As is the case for any delegation between contracts, the two contracts need to maintain identical storage layouts.
Unlike in other proxies throughout the codebase, SecurityToken
and STGetter
do not inherit from the same base storage contracts to enforce the storage layout. Rather, STGetter
uses the OZStorage
contract. The "OZ" presumably stands for "OpenZeppelin" because the contract mimics the storage layout of the ERC20
and ReentrancyGuard
contracts from OpenZeppelin.
Although the current code looks correct, maintaining the relationship between SecurityToken
and STGetter
's contract storage is both difficult and critical from a security perspective.
- Replace
OZStorage
withERC20
andReentrancyGuard
directly inSTGetter
inheritance. This has the advantage of making the code more transparent and eliminatingOZStorage
altogether. It will makeSTGetter
more expensive to deploy, but this is a one-time cost. - At a minimum, add comments to
OZStorage
,SecurityToken
,STGetter
that explains the issue, so future code maintainers remember to ensure consistent storage layout.
Severity: Minor
Status: Closed
Resolution: This issue has been closed with PolymathNetwork/polymath-core#688.
The following lines check whether an index is equal to an array length, but such a thing cannot occur except in error cases that would cause the transaction to revert anyway. Presumably there's a missing - 1
in both places:
code/contracts/modules/TransferManager/LTM/LockUpTransferManager.sol:L553
if ( _lockupIndex != _len) {
code/contracts/modules/TransferManager/LTM/LockUpTransferManager.sol:L563
if ( _userIndex != _len) {
In both cases, the erroneous check is harmless because it was just attempting to save gas anyway by avoiding a no-op swap.
Check against _len - 1
instead.
The creation of a threat model is beneficial when building smart contract systems as it helps to understand the potential security threats, assess risk, and identify appropriate mitigation strategies. This is especially useful during the design and development of a contract system as it allows to create a more resilient design which is more difficult to change post-development.
A threat model was created during the audit process in order to analyze the attack surface of the contract system and to focus review and testing efforts on key areas that a malicious actor would likely also attack. It consists of two parts: a high-level design diagram and component description that helps to understand the attack surface and a list of security stories describing the contract system.
- is in control of the account that deploys the polymath contract system.
- operates the polymath contract system.
- owns, manages and has full permissions to the polymath network registries (Feature-, Polymath-, Module-, SecuritytokenRegistry) and their proxies.
- can alter the addresses of core system contracts (polymath registry).
- can enable/disable system-wide features (feature registry).
- can globally allow security token issuers to use custom modules with their security tokens (feature registry).
- provides modules, module factories and deploys module proxies that are available to security tokens (module registry).
- controls which modules are generally available to issuers in the system.
- can make custom modules generally available to other issuers in the system (verifyModule).
- it might not be possible for issuers to distinguish a custom module from one that is maintained by polymath.
- does not necessarily own custom modules (can be created by anyone; usually owned by issuer to be available to security tokens).
- owns the Security Token Factory that is used to deploy security tokens.
- deploys
SecurityTokenProxy
and transfers ownership to issuer.- cannot interfere with security tokens once they are generated and ownership has been transferred.
- trust assumption: issuer remains in full control of their security token.
- polymath still remains some level of control via referenced contracts (e.g. oracles, registries, factories, ...).
- controls the issuance of new security tokens and can stop people from generating new tokens.
- remains control over some parts of the contracts even when they are paused.
- deploys and remains in full control of oracle interface contracts.
- can manually override and freeze responses provided by the external oracle provider.
- sanity checks oracle responses.
- manages the fee system (module fees; potentially usage fees).
- owns the poly token.
- controls conversion rates.
- are a trusted entity in the system.
- can be overridden by Polymath.
- are controlled by Polymath oracle interface contracts.
- can register new token symbols/tickers.
- can generate a new security token and finally owns it.
- manages the security token: minting, add/remove modules.
- can delegate permissions about certain aspects of the security token to other entities (admin/operate permissions).
- can add custom modules if allowed by the feature-registry.
- can create/deploy/maintain and use custom modules (if allowed by feature registry).
- pays fees.
The investors are the external users that initially have to be verified off-chain. Depending on the configuration of the security token they are whitelisted on-chain to be able to transact the tokens. They do not have any special permissions and by design they only interact with the tokens and the interest distribution.
- can own sec tokens shares.
- can transfer tokens.
- does not have any major permissions.
- can vote in platform.
- can call for dividend payments.
This role is assigned separately for each module by the SecurityToken owner in permission manager. Operator from one module does not have any rights in any other module. The Operator role protects access to critical functionality.
This role is assigned separately for each module by the SecurityToken owner in permission manager. Admin from one module does not have any rights in any other module. The Admin role protects access to functionality that is even more critical than what is protected by the Operator role.
- can deploy and register custom modules if allowed by global feature registry. Modules can only be used if they are verified by polymath.
The following contracts are ownable:
- Registries / RegistryProxies - owned by Polymath
- SecurityTokenFactory - owned by Polymath
- SecurityTokenProxy - owned by issuer
- Modules/ModuleProxies/ModuleFactories - owned by Polymath
- Custom Modules/ModuleProxies/ModuleFactories - owned by issuer or anyone if allowed by global feature registry.
Assets must be protected, as potential threats could result in considerable loss for the actors, can erode the system's trust or have legal repercussions. The following assets were identified:
- Private keys: a fair number of contracts are deployed in the system. different private keys have special control over features in the contracts.
- Deployed contracts: the instances of the on chain deployed contracts are considered an asset.
- Documents: documents with information handled by the ERC 1643 implementation; the documents are not added on chain, however metadata (Name, URI, DocumentHash) is stored in the contracts.
- Registries: Owned by polymath, the registries are the backbone of the system. They provide access to the core functionality and deployed contracts.
- SecurityTokens: Owned by the issuer, the security tokens themselves with their security token configuration are considered an asset.
- Modules: Owned by polymath - or if allowed by the global feature registry, owned by any other entity in the system - individual modules and their functionality are considered an asset.
- Balances: ETH and ERC20 tokens balances managed by components of the system.
- Partitions/Shares/Stake: represent different slates of investor balances with specific sets of rules.
The following diagram gives a high-level overview of the system, including its core components and an initial list of actors, public interfaces and external calls as well as notes taken during the audits exploration phase.
The following diagram gives a high-level overview of the security token generation process, the patterns used and the relationship to the module life-cycle.
This section provides an excerpt of various security stories the team explored during the audit. Security stories aim to bring transparency about security relevant characteristics of the system, help to understand trust assumptions and describe potential high-level threats to the system. The goal is to spark security discussions, document them as part of a continuous process and even use them as input for internal SDL security practices. Relevant issues derived from the stories are listed in the Issue Details section.
- Polymath is in control of registries forming the backbone of the system, may modify the location of other system components (
PolymathRegistry
), enable and disable features (FeatureRegistry
), register and verify modules that are available to security tokens (ModuleRegistry
) and may front-run users by changing attributes/parts of the system. - Polymath is in control of the security token generation, may disable token generation, delist and modify tokens reference in the registry.
- Polymath is in control of the security token registration, may disable ticker registration, modify/delete registered tickers.
- Polymath does not retain direct control of deployed security tokens once ownership was transferred to the issuer.
- Polymath remains indirect control of the security token via system components referenced in the security token (oracle, registries, ...) and may therefore indirectly interfere with the security token.
- Polymath can delist a security token and immediately list a new one with the same ticker. This might be a problem if done, for example, immediately before a hyped token sale if buyers are going to find the token via Polymath's registry rather than directly by address. The buyers could then end up buying the wrong token.
- Polymath is in control of Oracles and may freeze or manually override prices.
- Polymath is in control of the fee system and may arbitrarily change fees (front-run).
- An issuer can register arbitrary and potentially insulting ticker symbols which may cause manual effort to be corrected.
- An issuer can create and add custom modules to security tokens that are unverified by Polymath if custom modules are generally allowed in the system, which may undermine trust assumptions.
- An oracle can try to provide untruthful responses to manipulate prices in the system. Oracle responses are controlled by contracts owned by Polymath.
- Other participants can observe ticker/token registrations.
- Other participants can observe and collect document information linked to security tokens.
- Other participants can attempt to register custom modules containing dangerous functionality and try to get them verified by Polymath to make them available for other participants if custom modules are allowed by the system.
- Other participants can attempt to spam the custom module registration to cause DoS scenarios (out of gas) for other participants if custom modules are allowed by the system.
- Other participants may be able to claim ownership of contracts that were left uninitialized by accident (e.g.
SecurityToken
,SecurityTokenProxy
, ...) - Other participants can attempt to perform ticker squatting, trying to register popular ticker names before others do, sell them or create manual effort due to filed disputes.
- Other participants can attempt to front-run ticker registrations which can result in a frustrating experience for potential issuers or result in manual effort due to filed disputes.
- Other participants may attempt to get control of system components by getting hold of other participants' private keys (extortion, social engineering, and traditional information security threats).
Several tools were used to perform automated analysis of the reviewed contracts. These issues were reviewed by the audit team, and relevant issues are listed in the Issue Details section.
Mythril is a security analysis tool for Ethereum smart contracts. It uses concolic analysis to detect various types of issues. The tool was used for automated vulnerability discovery for all audited contracts and libraries. More details on Mythril's current vulnerability coverage can be found here.
The raw output of the Mythril vulnerability scan can be found here.
MythX is a security analysis API for Ethereum smart contracts. It performs multiple types of analysis, including fuzzing and symbolic execution, to detect many common vulnerability types. The tool was used for automated vulnerability discovery for all audited contracts and libraries. More details on MythX can be found at mythx.io.
Harvey is a greybox-fuzzer that uses light-weight program instrumentation to record the execution path for each tested input and compute its path identifier. The approach was developed by Valentin Wüstholz and Maria Christakis and published in their 2018 paper.
Ethlint is an open source project for linting Solidity code. Only security-related issues were reviewed by the audit team.
The raw output of the Ethlint vulnerability scan can be found here. The raw output of the Solhint scan can be found here.
Surya is a utility tool for smart contract systems. It provides a number of visual outputs and information about the structure of smart contracts. It also supports querying the function call graph in multiple ways to aid in the manual inspection and control flow analysis of contracts.
- A complete list of functions with their visibility and modifiers can be found here.
- A visualization of the system's function call graph can be found here (dot).
- A visualization of the system's inheritance graph can be found here (dot).
Code coverage metrics indicate the amount of lines/statements/branches that are covered by the test-suite. It's important to note that "100% test coverage" is not a silver bullet. Our review also included an inspection of the test suite to ensure that testing included important edge cases. Be aware that code coverage does not provide information about the individual test-cases quality.
A fork of the Solidity-Coverage tool was used to measure the portion of the code base exercised by the test suite, and identify areas with little or no coverage. Specific sections of the code where necessary test coverage is missing are included in the Issue Details section.
The project is using the automated testing framework provided by Truffle. The test-suite is evaluating 1556 individual tests and the test-suite passed without errors. The corresponding console output can be found here.
A code coverage report was generated and is provided along other tool output. The test coverage results can be viewed by opening the index.html
file from the coverage report directory in a browser. Please find a summary of the coverage results below.
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Lines |
---|---|---|---|---|---|
contracts/ | 96.74 | 88.98 | 93.2 | 97.14 | |
FeatureRegistry.sol | 100 | 50 | 100 | 100 | |
ModuleRegistry.sol | 100 | 90.79 | 100 | 100 | |
Pausable.sol | 100 | 75 | 100 | 100 | |
PolymathRegistry.sol | 100 | 100 | 100 | 100 | |
ReclaimTokens.sol | 100 | 75 | 100 | 100 | |
STRGetter.sol | 97.26 | 83.33 | 94.44 | 97.62 | 50,249 |
SecurityTokenRegistry.sol | 94.39 | 90.32 | 88.24 | 94.91 | ... 846,847,848 |
contracts/datastore/ | 97.89 | 73.68 | 100 | 98.04 | |
DataStore.sol | 97.78 | 76.47 | 100 | 97.95 | 303,315,339 |
DataStoreFactory.sol | 100 | 50 | 100 | 100 | |
DataStoreProxy.sol | 100 | 50 | 100 | 100 | |
DataStoreStorage.sol | 100 | 100 | 100 | 100 | |
contracts/interfaces/ | 100 | 100 | 100 | 100 | |
IBoot.sol | 100 | 100 | 100 | 100 | |
ICheckPermission.sol | 100 | 100 | 100 | 100 | |
IDataStore.sol | 100 | 100 | 100 | 100 | |
IFeatureRegistry.sol | 100 | 100 | 100 | 100 | |
IModule.sol | 100 | 100 | 100 | 100 | |
IModuleFactory.sol | 100 | 100 | 100 | 100 | |
IModuleRegistry.sol | 100 | 100 | 100 | 100 | |
IOracle.sol | 100 | 100 | 100 | 100 | |
IOwnable.sol | 100 | 100 | 100 | 100 | |
IPoly.sol | 100 | 100 | 100 | 100 | |
IPolymathRegistry.sol | 100 | 100 | 100 | 100 | |
ISTFactory.sol | 100 | 100 | 100 | 100 | |
ISTO.sol | 100 | 100 | 100 | 100 | |
ISecurityToken.sol | 100 | 100 | 100 | 100 | |
ISecurityTokenRegistry.sol | 100 | 100 | 100 | 100 | |
ITransferManager.sol | 100 | 100 | 100 | 100 | |
IUSDTieredSTOProxy.sol | 100 | 100 | 100 | 100 | |
IUpgradableTokenFactory.sol | 100 | 100 | 100 | 100 | |
IVoting.sol | 100 | 100 | 100 | 100 | |
contracts/interfaces/token/ | 100 | 100 | 100 | 100 | |
IERC1410.sol | 100 | 100 | 100 | 100 | |
IERC1594.sol | 100 | 100 | 100 | 100 | |
IERC1643.sol | 100 | 100 | 100 | 100 | |
IERC1644.sol | 100 | 100 | 100 | 100 | |
contracts/libraries/ | 92.71 | 81.06 | 91.67 | 93.16 | |
DecimalMath.sol | 100 | 100 | 100 | 100 | |
Encoder.sol | 66.67 | 100 | 66.67 | 66.67 | 21,25 |
TokenLib.sol | 88.46 | 80.95 | 90 | 88.73 | ... 455,473,476 |
Util.sol | 100 | 100 | 100 | 100 | |
VersionUtils.sol | 96.77 | 65 | 100 | 100 | |
VolumeRestrictionLib.sol | 100 | 91.67 | 100 | 100 | |
contracts/modules/ | 92.63 | 79.03 | 91.67 | 91.43 | |
Module.sol | 89.47 | 68.75 | 84.62 | 84.62 | 49,50,54,55 |
ModuleFactory.sol | 91.07 | 82.35 | 94.44 | 91.07 | ... 160,199,200 |
UpgradableModuleFactory.sol | 100 | 83.33 | 100 | 100 | |
contracts/modules/Burn/ | 100 | 100 | 100 | 100 | |
IBurn.sol | 100 | 100 | 100 | 100 | |
contracts/modules/Checkpoint/ | 100 | 100 | 100 | 100 | |
ICheckpoint.sol | 100 | 100 | 100 | 100 | |
contracts/modules/Checkpoint/Dividend/ | 98.39 | 86.21 | 95.83 | 98.41 | |
DividendCheckpoint.sol | 98.39 | 86.21 | 95.83 | 98.41 | 422,423 |
contracts/modules/Checkpoint/Dividend/ERC20/ | 100 | 83.33 | 100 | 100 | |
ERC20DividendCheckpoint.sol | 100 | 85 | 100 | 100 | |
ERC20DividendCheckpointFactory.sol | 100 | 100 | 100 | 100 | |
ERC20DividendCheckpointProxy.sol | 100 | 50 | 100 | 100 | |
ERC20DividendCheckpointStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/Checkpoint/Dividend/Ether/ | 100 | 93.75 | 100 | 100 | |
EtherDividendCheckpoint.sol | 100 | 96.67 | 100 | 100 | |
EtherDividendCheckpointFactory.sol | 100 | 100 | 100 | 100 | |
EtherDividendCheckpointProxy.sol | 100 | 50 | 100 | 100 | |
contracts/modules/Checkpoint/Voting/ | 100 | 100 | 100 | 100 | |
VotingCheckpoint.sol | 100 | 100 | 100 | 100 | |
contracts/modules/Checkpoint/Voting/PLCR/ | 96.52 | 87.5 | 100 | 94.96 | |
PLCRVotingCheckpoint.sol | 96.15 | 88.57 | 100 | 94.23 | ... 296,297,298 |
PLCRVotingCheckpointFactory.sol | 100 | 100 | 100 | 100 | |
PLCRVotingCheckpointProxy.sol | 100 | 50 | 100 | 100 | |
PLCRVotingCheckpointStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/Checkpoint/Voting/Transparent/ | 95.4 | 80.36 | 100 | 93.68 | |
WeightedVoteCheckpoint.sol | 94.74 | 81.48 | 100 | 92.5 | ... 224,225,226 |
WeightedVoteCheckpointFactory.sol | 100 | 100 | 100 | 100 | |
WeightedVoteCheckpointProxy.sol | 100 | 50 | 100 | 100 | |
WeightedVoteCheckpointStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/PermissionManager/ | 100 | 82.35 | 100 | 100 | |
GeneralPermissionManager.sol | 100 | 84.38 | 100 | 100 | |
GeneralPermissionManagerFactory.sol | 100 | 100 | 100 | 100 | |
GeneralPermissionManagerProxy.sol | 100 | 50 | 100 | 100 | |
GeneralPermissionManagerStorage.sol | 100 | 100 | 100 | 100 | |
IPermissionManager.sol | 100 | 100 | 100 | 100 | |
contracts/modules/STO/ | 100 | 50 | 100 | 100 | |
STO.sol | 100 | 50 | 100 | 100 | |
contracts/modules/STO/Capped/ | 100 | 90 | 100 | 100 | |
CappedSTO.sol | 100 | 92.11 | 100 | 100 | |
CappedSTOFactory.sol | 100 | 100 | 100 | 100 | |
CappedSTOProxy.sol | 100 | 50 | 100 | 100 | |
CappedSTOStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/STO/PreSale/ | 100 | 83.33 | 100 | 100 | |
PreSaleSTO.sol | 100 | 87.5 | 100 | 100 | |
PreSaleSTOFactory.sol | 100 | 100 | 100 | 100 | |
PreSaleSTOProxy.sol | 100 | 50 | 100 | 100 | |
PreSaleSTOStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/STO/USDTiered/ | 98.68 | 76.53 | 100 | 98.79 | |
USDTieredSTO.sol | 98.62 | 77.08 | 100 | 98.71 | 556,597,642 |
USDTieredSTOFactory.sol | 100 | 100 | 100 | 100 | |
USDTieredSTOProxy.sol | 100 | 50 | 100 | 100 | |
USDTieredSTOStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/TransferManager/ | 37.5 | 100 | 60 | 54.55 | |
TransferManager.sol | 37.5 | 100 | 60 | 54.55 | 35,42,43,44,45 |
contracts/modules/TransferManager/BTM/ | 99.13 | 96.88 | 97.14 | 99.21 | |
BlacklistTransferManager.sol | 99.05 | 98.39 | 96.88 | 99.12 | 433 |
BlacklistTransferManagerFactory.sol | 100 | 100 | 100 | 100 | |
BlacklistTransferManagerProxy.sol | 100 | 50 | 100 | 100 | |
BlacklistTransferManagerStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/TransferManager/CTM/ | 96.3 | 87.5 | 90.91 | 96.67 | |
CountTransferManager.sol | 94.12 | 100 | 87.5 | 94.12 | 56 |
CountTransferManagerFactory.sol | 100 | 100 | 100 | 100 | |
CountTransferManagerProxy.sol | 100 | 50 | 100 | 100 | |
CountTransferManagerStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/TransferManager/GTM/ | 99.32 | 87.1 | 100 | 99.41 | |
GeneralTransferManager.sol | 99.25 | 88.33 | 100 | 99.35 | 363 |
GeneralTransferManagerFactory.sol | 100 | 100 | 100 | 100 | |
GeneralTransferManagerProxy.sol | 100 | 50 | 100 | 100 | |
GeneralTransferManagerStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/TransferManager/LTM/ | 88.73 | 72.41 | 83.33 | 89.54 | |
LockUpTransferManager.sol | 87.88 | 73.21 | 82.05 | 88.57 | ... 516,626,690 |
LockUpTransferManagerFactory.sol | 100 | 100 | 100 | 100 | |
LockUpTransferManagerProxy.sol | 100 | 50 | 100 | 100 | |
LockUpTransferManagerStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/TransferManager/MATM/ | 94.69 | 82 | 95.65 | 95.2 | |
ManualApprovalTransferManager.sol | 94.17 | 83.33 | 95 | 94.64 | ... 268,269,390 |
ManualApprovalTransferManagerFactory.sol | 100 | 100 | 100 | 100 | |
ManualApprovalTransferManagerProxy.sol | 100 | 50 | 100 | 100 | |
ManualApprovalTransferManagerStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/TransferManager/PTM/ | 97.44 | 92.86 | 92.86 | 97.62 | |
PercentageTransferManager.sol | 96.55 | 100 | 90.91 | 96.55 | 69 |
PercentageTransferManagerFactory.sol | 100 | 100 | 100 | 100 | |
PercentageTransferManagerProxy.sol | 100 | 50 | 100 | 100 | |
PercentageTransferManagerStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/TransferManager/VRTM/ | 89.66 | 87.93 | 87.27 | 90.32 | |
VolumeRestrictionTM.sol | 89.19 | 88.6 | 86.54 | 89.7 | ... 9,1256,1257 |
VolumeRestrictionTMFactory.sol | 100 | 100 | 100 | 100 | |
VolumeRestrictionTMProxy.sol | 100 | 50 | 100 | 100 | |
VolumeRestrictionTMStorage.sol | 100 | 100 | 100 | 100 | |
contracts/modules/Wallet/ | 98.19 | 84.88 | 100 | 98.37 | |
VestingEscrowWallet.sol | 98.08 | 85.71 | 100 | 98.24 | 120,121,122 |
VestingEscrowWalletFactory.sol | 100 | 100 | 100 | 100 | |
VestingEscrowWalletProxy.sol | 100 | 50 | 100 | 100 | |
VestingEscrowWalletStorage.sol | 100 | 100 | 100 | 100 | |
Wallet.sol | 100 | 100 | 100 | 100 | |
contracts/proxy/ | 79.49 | 72.73 | 79.17 | 79.07 | |
ModuleRegistryProxy.sol | 100 | 100 | 100 | 100 | |
OwnedProxy.sol | 33.33 | 16.67 | 37.5 | 30.77 | ... 77,85,86,87 |
OwnedUpgradeabilityProxy.sol | 100 | 87.5 | 100 | 100 | |
Proxy.sol | 100 | 100 | 100 | 100 | |
SecurityTokenRegistryProxy.sol | 100 | 100 | 100 | 100 | |
UpgradeabilityProxy.sol | 100 | 100 | 100 | 100 | |
contracts/tokens/ | 90.58 | 70.77 | 93.86 | 90.2 | |
OZStorage.sol | 50 | 100 | 50 | 50 | 15 |
STFactory.sol | 91.67 | 50 | 71.43 | 92.31 | 184,185,195,196 |
STGetter.sol | 74.29 | 80 | 90.48 | 74.07 | ... 284,285,287 |
SecurityToken.sol | 95.28 | 76.25 | 97.59 | 94.9 | ... 936,937,945 |
SecurityTokenProxy.sol | 100 | 50 | 100 | 100 | |
SecurityTokenStorage.sol | 100 | 100 | 100 | 100 | |
All files | 95.12 | 83.35 | 94.62 | 95.18 |
- 95.12% of Statements, 83.35% of Branches, 94.62% of Functions and 95.18% Lines are covered by test-cases
- It is important to provide an extensive test-suite that covers all statements, functions and paths of the contract system. Any contract system that is used on the main net should have as a minimum requirement a 100% test coverage especially. Please note that a seemingly high coverage rate of 95% means that 5% of Statements are uncovered. A few percent can easily translate to a couple of hundred uncovered statements for large codebases.
- The Branch coverage of 83.35% (257 uncovered branches) is low.
- Negative testing of
require()
assertions or edge-cases is often missing contributing to the low branch coverage. It is suggested to add positive and negative tests especially for important language constructs likerequire
that often perform input validation. - Some functions are completely uncovered or exempt from testing (see below).
The following contracts are not part of the coverage statistics collection:
- contracts/storage - contains storage layouts for other modules but also some logic (initialization) that should be tested
- contracts/oracles - contains oracle interface contracts written and maintained by polymath that should be tested.
- contracts/modules/experimental - new experimental modules that should be tested.
- contracts/external - contains externally provided OraclizeI/IMedianizer contracts that should be mocked for testing.
- contracts/helpers/polytoken - is the ERC20 POLY token that should be tested but is out of scope for this audit.
- contracts/libraries/KindMath, BokkyPooBahsDateTimeLibrary - are 3rd party libraries with unclear test/coverage status.
- STRGetter
- uncovered:
getExpiryLimit()
- partially:
_ownerInTicker()
- uncovered:
- SecurityTokenRegistry
- uncovered:
updateFromRegistry(), getSecurityTokenLaunchFee(), getTickerRegistrationFee(), setGetterRegistry(), removeProtocolFactory()
- partially:
generateNewSecurityToken(), transferTickerOwnership()
- uncovered:
- Encoder
- uncovered:
getKey()
- uncovered:
- TokenLib
- uncovered:
hash(), extractSigner()
- uncovered:
- Module
- uncovered:
modifier(onlyFactoryOwner,onlyFactoryOrOwner)
- uncovered:
- *ModuleFactory
- uncovered:
changeTags()
- partially:
changeSTVersionBounds(), usageCostInPoly()
- uncovered:
- DividendCheckpoint
- uncovered:
isClaimed()
- uncovered:
- TransferManager
- uncovered:
getTokensByPartition(), getPartitions()
- uncovered:
- BlacklistTransferManager
- uncovered:
getAllBlacklists()
- uncovered:
- CountTransferManager
- uncovered:
verifyTransfer()
- uncovered:
- GeneralTransferManager
- partially:
_modifyInvestorFlag()
- partially:
- LockupTransferManager
- uncovered:
verifyTransfer(), addLockUpByNameMulti(), removeLockUpFromUser(), removeLockupTypeMulti(), modifyLockUpTypeMulti(), getListOfAddresses(), getLockupsNamesToUser()
- partially:
_modifyLockUpType(), _addNewLockUpType(), getTokensByPartition()
- uncovered:
- ManualApprovalTransferManager
- uncovered:
modifyManualApprovalMulti()
- partially:
_modifyManualApproval()
- uncovered:
- PercentageTransferManager
- uncovered:
verifyTransfer()
- uncovered:
- VolumeRestrictionTM
- uncovered:
addIndividualDailyRestrictionMulti(), removeIndividualDailyRestrictionMulti(),removeDefaultDailyRestriction(), modifyIndividualDailyRestrictionMulti(), modifyDefaultRestriction(), modifyDefaultDailyRestriction(), getPermissions()
- partially:
verifyTransfer(), addIndividualDailyRestriction()
- uncovered:
- VestingEscrowWallet
- uncovered:
getTreasuryWallet()
- uncovered:
- OwnedProxy
- uncovered:
modifier(ifOwner), proxyOwner(), implementation(), transferProxyOwnership()
- uncovered:
- OZStorage
- uncovered:
totalSupply()
- uncovered:
- STFactory
- uncovered:
updateDefaultTransferManager(), updateDefaultDataStore()
- uncovered:
- STGetter
- uncovered:
partitionsOf(), _appendPartition()
- uncovered:
- SecurityToken
- uncovered:
modifier(onlyTokenFactory), canTransferFrom(), disableController(), freezeIssuance()
- partially:
_canTransfer(), _executeTransfer()
- uncovered:
As part of this audit we have further reviewed the business logic in version 2.2.0 of the LockUpTransferManager
, BlacklistTransferManager
and VestingEscrowWallet
to ensure it matches the version 3.0.0 implementations. Polymath created the diffs and provided them to the audit team. They can be found here. Whilst we have audited that the business logic is functionally equivalent the correctness of these modules relies on the surrounding 2.2.0 code which has not been audited as part of this review. The following changes have been noted:
- Many changes are related to solidity 0.4.x vs. 0.5.x.
- Minor changes to delegator permissions
OPERATOR
andADMIN
. - Insignificant visibility differences (
public
vs.external
). - Small internal functions have been removed and their logic is inlined instead.
The SHA1 hash of the source code files in scope of the audit are listed in the table below.
File Name | SHA-1 Hash |
---|---|
contracts/FeatureRegistry.sol | 6eb07dfc15c6eac6d72fa1e856f86fb2e6e4e553 |
contracts/Migrations.sol | 3d3d5e4f8d8654dc16efd17ac3494cde3b124fc5 |
contracts/ModuleRegistry.sol | 72a5c25db93193603880a82118c0d17c989fe517 |
contracts/Pausable.sol | 89b3e08a3b4a12e935cf08fb713707d08ac95d92 |
contracts/PolymathRegistry.sol | 3f079b9e9e9bb567c04ccc033ef91c1039b76e07 |
contracts/ReclaimTokens.sol | 85620841940605c1f94ee4cd74208b3675463eb1 |
contracts/STRGetter.sol | 2dd20ebf94c70d164fe24c32dc67e0609deb8820 |
contracts/SecurityTokenRegistry.sol | 3b5ffaddfe371954c458f8dc6b01507bc9af543d |
contracts/datastore/DataStore.sol | 6978f43dbf995aaf453911462a29afe4a146abe8 |
contracts/datastore/DataStoreFactory.sol | c0a9308234c73d9628702975bc1bb231e93fd1a4 |
contracts/datastore/DataStoreProxy.sol | db14fc8c4363bfdb6d8214bf6fe7c0b5a0e11121 |
contracts/datastore/DataStoreStorage.sol | f0985b4af4fdcd63a9505fca80199b0b6068a6d4 |
contracts/external/IMedianizer.sol | cafcc374ec685f2291a843c98f51401f740e52c2 |
contracts/external/oraclizeAPI.sol | c84fcbf4829b972bac7358f496a7fce600a6cf14 |
contracts/helpers/PolyToken.sol | 390dc1245ec6ab0358f7421c9562e0fb4eedcd97 |
contracts/interfaces/IBoot.sol | 48dd670fe3461955dfed6ab62638c61d97573c41 |
contracts/interfaces/ICheckPermission.sol | e2a69c51af575bed0a27460a44530dcc26e6bc17 |
contracts/interfaces/IDataStore.sol | c88cd1e8070af809b75fe096022c362a524b745a |
contracts/interfaces/IFeatureRegistry.sol | a4c9e5c062370a777688a5f9de22e03a8c5f8873 |
contracts/interfaces/IModule.sol | bb7f108b785f62f22b8be7ae44139403ea8769c9 |
contracts/interfaces/IModuleFactory.sol | 80eb80e18f6bf5a1e30e55d56221ca2b3a1fafc8 |
contracts/interfaces/IModuleRegistry.sol | 8240a2a1e7a61431768746c61c30d5280d51ecad |
contracts/interfaces/IOracle.sol | 3b56748248e4b2ae01116010e8ae168381c3918a |
contracts/interfaces/IOwnable.sol | 5bd12c84bbb534c7d8bbcebe1c3ac03df6e5b4a4 |
contracts/interfaces/IPoly.sol | a2fd2f6aabe3d52c6dc033e5ec713d1b521ccb9b |
contracts/interfaces/IPolymathRegistry.sol | cf9527df106a932ab54ff219c25cb5f1d7039970 |
contracts/interfaces/ISTFactory.sol | 8f3e5148ee98ed8411343f99f5e2530cb918b72c |
contracts/interfaces/ISTO.sol | a81a894300a4c5f7d1a21f793bee274c5ce636d0 |
contracts/interfaces/ISecurityToken.sol | 0fcacc9cecf11b7a5da16f24dbb9b7189e9426f5 |
contracts/interfaces/ISecurityTokenRegistry.sol | 2ce5029ef544a5462fc3498be131aa13fd767677 |
contracts/interfaces/ITransferManager.sol | 729dae77edc34f5982cb072406ecbaf6c0029d68 |
contracts/interfaces/IUSDTieredSTOProxy.sol | 8c96ef686be2dbec63d36c6a06ac2d2ac7c2f5d8 |
contracts/interfaces/IUpgradableTokenFactory.sol | 252626e0129fd999f55c0314fe53a7c0fb613511 |
contracts/interfaces/IVoting.sol | 82616bd359a8e491cc1270c50c96deda78a3e9f2 |
contracts/interfaces/token/IERC1410.sol | 108f2850523289c17c4c77e7fd4c72ec3fc47358 |
contracts/interfaces/token/IERC1594.sol | 00b44268bd3bdfffe4cd47d2d2e40e10dacd6338 |
contracts/interfaces/token/IERC1643.sol | 9293365b6ead5f78726f3ded0cf7d9b1ae05e04e |
contracts/interfaces/token/IERC1644.sol | 55a26529da680652c7f6a3ea9a2a3de190a1216f |
contracts/libraries/BokkyPooBahsDateTimeLibrary.sol | b00a2888d7b718dd4ac6dc8a009ac0960d982464 |
contracts/libraries/DecimalMath.sol | e561ef0dc7cc0ee16aa7949bf7e54d3496c116ec |
contracts/libraries/Encoder.sol | 6113594a29088d5cd7a4aa61885901f48ed75556 |
contracts/libraries/KindMath.sol | 49e5106f23fa40a07ce7db54cd740be7437eec70 |
contracts/libraries/TokenLib.sol | c66f623f4875644d77dac541fe7cce8160bea2d0 |
contracts/libraries/Util.sol | 6bda132058a98c7d88d0349a2283484959ee5357 |
contracts/libraries/VersionUtils.sol | 7de9cfc1ec7c10ebb628ad043e42f1a8e66d1919 |
contracts/libraries/VolumeRestrictionLib.sol | b5f38144c0da1c28acf706f073e4ef8f330be87b |
contracts/mocks/Dummy/DummySTO.sol | 35fea506d1a5e9bdbf1e25f7c993d689b0998dd6 |
contracts/mocks/Dummy/DummySTOFactory.sol | b3e4d6c8af1a9048209f1b85978286b81a454aeb |
contracts/mocks/Dummy/DummySTOProxy.sol | ca60135c227b940847793fb08aac24e4eb0d889a |
contracts/mocks/Dummy/DummySTOStorage.sol | 9d76e213811253ca67d04ef07519d6bd83b25766 |
contracts/mocks/FunctionSigClash1.sol | d8482c3241ed2be9638938052c3cbe6775df2fcc |
contracts/mocks/FunctionSigClash2.sol | bfd89a2981b578a820bfb8b7bf25c7c5f47afaee |
contracts/mocks/MockBurnFactory.sol | dc3155ac6d0167863b05153949ae0f00b735e677 |
contracts/mocks/MockCountTransferManager.sol | 2207fcecf1c3098472555831de3b2fa0ce9a4f15 |
contracts/mocks/MockFactory.sol | 8019db0c439d4b6d40590fe0d0e414183b7ed1b5 |
contracts/mocks/MockModuleRegistry.sol | 6efefc04d0f8569a00c28e37a81c7eec04ddab35 |
contracts/mocks/MockOracle.sol | 83f97501c8f78d7e336345f71bba76b7351ddb20 |
contracts/mocks/MockPolyOracle.sol | 3a0295c547d658a92d3699607290014834ba55cb |
contracts/mocks/MockRedemptionManager.sol | ac5fa55ed85358d0dcde7bed03b7f9ae21186b89 |
contracts/mocks/MockSTGetter.sol | 8470f52b08cd8a5c9f8e79db7e6085116d4cdcaf |
contracts/mocks/MockSecurityTokenLogic.sol | 95fc212040cdfc54b6db3b4dee76af9105ba560d |
contracts/mocks/MockWrongTypeFactory.sol | f5d7e701f2a49567c4511feca9d01b678ab2df20 |
contracts/mocks/PolyTokenFaucet.sol | cba48fda96073d5c67c75bd2319ae06c699c8b28 |
contracts/mocks/SecurityTokenMock.sol | 62438ca46d5a75f23c6dd91ae507bcf85f5d34c9 |
contracts/mocks/SecurityTokenRegistryMock.sol | d44ed787558d454522cf00f3abc33c8000ec6cec |
contracts/mocks/TestSTOFactory.sol | 64705949cd53b2cdf6b051bf83810a7399622bbe |
contracts/modules/Burn/IBurn.sol | 1d8febfe2a4d66cc17786574e35423ad97aa5806 |
contracts/modules/Checkpoint/Dividend/DividendCheckpoint.sol | e3ebe55d16afc76b2ec1c123fc63d6187b1fb619 |
contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpoint.sol | 7aca4a6c96d73eff535fca2e45378f92bbf7eb98 |
contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointFactory.sol | ca89f5c7d800a9459830b6ddcd119ee7809c2924 |
contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointProxy.sol | c640fa7c7a22affb8e278ca87dfd7ad878a428bd |
contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointStorage.sol | 2111bf5bc55630858b9057b2be772d084f41f9c9 |
contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpoint.sol | d78a164391e60610e88601644e7b659695282742 |
contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointFactory.sol | 6130cbd004bb766625cef9ae6c8917982848a049 |
contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointProxy.sol | 758c989eafb1d49e4024862402e2d6974bc646b6 |
contracts/modules/Checkpoint/ICheckpoint.sol | 69d9d19318f3fa26a17494ec2a6643c96d382e9d |
contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpoint.sol | b7fe1a9790fba150a3a5d877b5ce764aec911ea8 |
contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointFactory.sol | ff8ed7af494dacdd1523b71830b6aad5ac48ebd7 |
contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointProxy.sol | f8d68c2e1badec3d511c7085fb3ad00ee83713fa |
contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointStorage.sol | 6ef8b528f6a458ad9452d329c8cb1a04226f4135 |
contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpoint.sol | 7a7ee3a77522d8a9e87e0d50c247bcb63933d85c |
contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointFactory.sol | 74dfa812b239d7621b9b049578e706a74673a9b9 |
contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointProxy.sol | 8431f3becdd1dd27c5ffb9d821a74c02b7fcadc8 |
contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointStorage.sol | 426b71a2d392e9b8c4ee7eab9ec8c42dc1e69989 |
contracts/modules/Checkpoint/Voting/VotingCheckpoint.sol | 1e61a190b43431f5a687b82ce105a58662ff7b32 |
contracts/modules/Experimental/Burn/TrackedRedemption.sol | e707351a092080708829b0584b86012827e1d5a0 |
contracts/modules/Experimental/Burn/TrackedRedemptionFactory.sol | c7f4ee146064f3083d2a4703332dbfee40770b06 |
contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol | 95907eb423e89e71b75d5f5437c7abc6c1585f9b |
contracts/modules/Experimental/Mixed/ScheduledCheckpointFactory.sol | f3f3445e25f36f93fd16dfac7ddb2f13b998692f |
contracts/modules/Experimental/TransferManager/KYCTransferManager.sol | b6925b29f9869765a466cccbb1dd08701272b2ba |
contracts/modules/Experimental/TransferManager/KYCTransferManagerFactory.sol | a338ceaba5e96074631bfcb58c072637de65266a |
contracts/modules/Experimental/TransferManager/SignedTransferManager.sol | a5eca562c086e70bd1cb64cea513c202adeab431 |
contracts/modules/Experimental/TransferManager/SignedTransferManagerFactory.sol | 19b7a56f99abbd132ffed43cf410f760f3b2d15e |
contracts/modules/Module.sol | bcc1d122caec436df7ef92fe4af3cf7aec46e921 |
contracts/modules/ModuleFactory.sol | ba78f80a7fc74c7dd603b4eb4f6d8823c1b9b440 |
contracts/modules/PermissionManager/GeneralPermissionManager.sol | 085c892ea3b41aabd9c0fdb271566f55a81387ac |
contracts/modules/PermissionManager/GeneralPermissionManagerFactory.sol | d70f682b982831e44101f076be2974580fd766b0 |
contracts/modules/PermissionManager/GeneralPermissionManagerProxy.sol | 6950049d869c1b0d39d538ceb7fdda52bb7169b4 |
contracts/modules/PermissionManager/GeneralPermissionManagerStorage.sol | 8f1f92a00c6a852e01b22d2b550ea7e2347cc299 |
contracts/modules/PermissionManager/IPermissionManager.sol | 92fb2bfc0cede22bd169a106f929c208ec257de8 |
contracts/modules/STO/Capped/CappedSTO.sol | 951e2f6ce5fd7a28181710c70a1c0be7c81c4037 |
contracts/modules/STO/Capped/CappedSTOFactory.sol | 7315b6935b638879bd58ec7f0c63a5a9d400a87d |
contracts/modules/STO/Capped/CappedSTOProxy.sol | 48d65c94491c9286e5593774d65cfbe70c86d2c1 |
contracts/modules/STO/Capped/CappedSTOStorage.sol | 523b09b120f182be954a01772ed9bdf1f6a556c3 |
contracts/modules/STO/PreSale/PreSaleSTO.sol | 4126f80eb142067a18fe3bcfde69f6aac213d0c4 |
contracts/modules/STO/PreSale/PreSaleSTOFactory.sol | 49576e9283f76e7a8e9369719dc55125f551923f |
contracts/modules/STO/PreSale/PreSaleSTOProxy.sol | 831ad28018290f7b48d1e9e04b4d4010761f51a4 |
contracts/modules/STO/PreSale/PreSaleSTOStorage.sol | 90e8c10f34cfafeb9cf9b90107738997fdb90de3 |
contracts/modules/STO/STO.sol | 9a13d33e03cc104fbdb55d5ee33e729e4e370c19 |
contracts/modules/STO/USDTiered/USDTieredSTO.sol | a99588f6d24a3c999ac2bd754551dcb781d51424 |
contracts/modules/STO/USDTiered/USDTieredSTOFactory.sol | 56f004a8715865a6e3dfbb955decee859f1db93c |
contracts/modules/STO/USDTiered/USDTieredSTOProxy.sol | b68a07d19104951cba83bd3a6fe1f68bbda183a7 |
contracts/modules/STO/USDTiered/USDTieredSTOStorage.sol | 918daba3046958f5ad4ec76a415accf2e3067d5b |
contracts/modules/TransferManager/BTM/BlacklistTransferManager.sol | 2874309dae84a81600bc2b3122a6701529c7097a |
contracts/modules/TransferManager/BTM/BlacklistTransferManagerFactory.sol | 3883bd95bbc65d9682c003f8b1c9daf6600cddb7 |
contracts/modules/TransferManager/BTM/BlacklistTransferManagerProxy.sol | 700ffadb11d5226da482e799b04643c81a09d5e0 |
contracts/modules/TransferManager/BTM/BlacklistTransferManagerStorage.sol | dc656d574f2558798359ed89c48a8cec498426e7 |
contracts/modules/TransferManager/CTM/CountTransferManager.sol | 903d30b552a13ea8d75eb1da85dd9987576e3bdf |
contracts/modules/TransferManager/CTM/CountTransferManagerFactory.sol | 537c3937f749d68fa3a172b9c474ff7f0ec6469d |
contracts/modules/TransferManager/CTM/CountTransferManagerProxy.sol | be435b2d7af86b78a1076fbac6aa58a07f50bfc4 |
contracts/modules/TransferManager/CTM/CountTransferManagerStorage.sol | 7c2fa10ba41d9d3e83d1169b0029c1304665b19f |
contracts/modules/TransferManager/GTM/GeneralTransferManager.sol | 9ce27054c2d24b30de0aca9f3963fb2ba19d85c9 |
contracts/modules/TransferManager/GTM/GeneralTransferManagerFactory.sol | a3addfa4782590d666a617cd94c6cb0c09d8dde8 |
contracts/modules/TransferManager/GTM/GeneralTransferManagerProxy.sol | 9d2b0a304ef08e7a7ddcebb3e8df63bf0ab6c7f8 |
contracts/modules/TransferManager/GTM/GeneralTransferManagerStorage.sol | 49c81ce1bf2cf702b13aad22ea0dd54cf41e04a3 |
contracts/modules/TransferManager/LTM/LockUpTransferManager.sol | aa3f0734f9ac362e0498cc6d136b35530d7e81b8 |
contracts/modules/TransferManager/LTM/LockUpTransferManagerFactory.sol | f5013c9283b2490388d5ae3daa16b8e8a1011400 |
contracts/modules/TransferManager/LTM/LockUpTransferManagerProxy.sol | f9004d720b98ff8f7073269f344e86533c660c86 |
contracts/modules/TransferManager/LTM/LockUpTransferManagerStorage.sol | e74eb033eaa8f82612143a11b469d7154664aeac |
contracts/modules/TransferManager/MATM/ManualApprovalTransferManager.sol | 179b3eac8ac1ddeb84e41b2bbc2bf50ad7a66f99 |
contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerFactory.sol | 5a10f30dc054d32fea568c40fe2838a66b47c234 |
contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerProxy.sol | 5a002ac39b2f058a49db3444f829b0e6070b260f |
contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerStorage.sol | 001d1060967ad06cf897f91c3587bfae7d4ccbf6 |
contracts/modules/TransferManager/PTM/PercentageTransferManager.sol | 2204753c49ce966be9f903d90f96cebf7b438091 |
contracts/modules/TransferManager/PTM/PercentageTransferManagerFactory.sol | b24afc7d6dc628ff9167781676b96d28187f464d |
contracts/modules/TransferManager/PTM/PercentageTransferManagerProxy.sol | c42604ffd2881359da150ea5a95d4e3fe3b68a5c |
contracts/modules/TransferManager/PTM/PercentageTransferManagerStorage.sol | 63d1fcd7cdf8e6ae98f053aee743cfc6c716f6d1 |
contracts/modules/TransferManager/TransferManager.sol | 7b23f67473f0f4ee9b0175bf246fad97dc9e78d1 |
contracts/modules/TransferManager/VRTM/VolumeRestrictionTM.sol | 2067e26d91a282db22ddd86543caaaf38778a609 |
contracts/modules/TransferManager/VRTM/VolumeRestrictionTMFactory.sol | 95ced5ad2bebd8a6a2d0e57ebcf0c200bc709667 |
contracts/modules/TransferManager/VRTM/VolumeRestrictionTMProxy.sol | 659b70802cd0001a7e0f5812a246213d9cc0c92c |
contracts/modules/TransferManager/VRTM/VolumeRestrictionTMStorage.sol | 7997f22900bba5b8e276f17510ac62462a7f422b |
contracts/modules/UpgradableModuleFactory.sol | d3d2042947c978cf2fe0ad40883103fc917af01e |
contracts/modules/Wallet/VestingEscrowWallet.sol | d52dee12c5ae0e8437927c12d546d939db50adf9 |
contracts/modules/Wallet/VestingEscrowWalletFactory.sol | 82778bd395c1fdb9fb795eb3b18494d2536124d4 |
contracts/modules/Wallet/VestingEscrowWalletProxy.sol | 6ab5ca89b14dd7746579d3f3f9bedba540287398 |
contracts/modules/Wallet/VestingEscrowWalletStorage.sol | 40f2c07402fca9f3ba3ebf215f74d81755694440 |
contracts/modules/Wallet/Wallet.sol | 7d0c44a0eb8efcb30c42d46607db9fa2400719c3 |
contracts/oracles/MakerDAOOracle.sol | a3c7529bf076297ab18dcb2b815ce49b67f612d5 |
contracts/oracles/PolyOracle.sol | aa2298f8f5950954ec750798831f449e4fd82763 |
contracts/oracles/StableOracle.sol | bf47421d8325b1eaf1b2e18f087646df04282c38 |
contracts/proxy/ModuleRegistryProxy.sol | 2b63e88096d2d4edf0a6cda65d2fd48baae42f53 |
contracts/proxy/OwnedProxy.sol | 773343c1462afa1d28462ab8f439332a67ad6384 |
contracts/proxy/OwnedUpgradeabilityProxy.sol | 850b4f5a1786f44deff91da2f1392df199ac2418 |
contracts/proxy/Proxy.sol | d966d38dcec975fe07e56a5e59f618df94fadd1a |
contracts/proxy/SecurityTokenRegistryProxy.sol | 549ff1edc87c8ed99617c4922fab893c29d2bcce |
contracts/proxy/UpgradeabilityProxy.sol | 1d563099b3ac6319a501ddcb54f22618a1ae3425 |
contracts/storage/EternalStorage.sol | ec958e8405e9347b7d0a0ad2853164387e6781b1 |
contracts/storage/modules/Checkpoint/Dividend/DividendCheckpointStorage.sol | 4a77473eb312eaabe2e2403b521724282b99b2f2 |
contracts/storage/modules/Checkpoint/Voting/VotingCheckpointStorage.sol | 4ba28863db2845e4ee58ffac9801c2071a1e1dd7 |
contracts/storage/modules/ModuleStorage.sol | 32df94d5b9ef3f9435eb1272ce03f2af9ba3e698 |
contracts/storage/modules/STO/ISTOStorage.sol | 242aa1b91d6b2ad015a57e226555ae34a58e7b0f |
contracts/storage/modules/STO/STOStorage.sol | eb6bfb238e81ef9529f7be186c84f2c9f4748fc0 |
contracts/tokens/OZStorage.sol | ab7e46383f105fc51e76a347a1b4c1a0aedf9c0f |
contracts/tokens/STFactory.sol | 1ce5b2862c6f90887445c81bb8ae2c10b5e7a4fe |
contracts/tokens/STGetter.sol | 981bc42db281f4a4d3e96fb729d6fd5cf410fdc3 |
contracts/tokens/SecurityToken.sol | eddebc4151539d5d1c9099821084f122d8fe2560 |
contracts/tokens/SecurityTokenProxy.sol | 282773ed99a4fe6daeb9104b4181afa12896b3cc |
contracts/tokens/SecurityTokenStorage.sol | fc355c0bbacc3927e9ff0c08650c0036514cfc38 |
Minor issues are generally subjective in nature, or potentially deal with topics like "best practices" or "readability". Minor issues in general will not indicate an actual problem or bug in code.
The maintainers should use their own judgment as to whether addressing these issues improves the codebase.
Medium issues are generally objective in nature but do not represent actual bugs or security problems.
These issues should be addressed unless there is a clear reason not to.
Major issues will be things like bugs or security vulnerabilities. These issues may not be directly exploitable, or may require a certain condition to arise in order to be exploited.
Left unaddressed these issues are highly likely to cause problems with the operation of the contract or lead to a situation which allows the system to be exploited in some way.
Critical issues are directly exploitable bugs or security vulnerabilities.
Left unaddressed these issues are highly likely or guaranteed to cause major problems or potentially a full failure in the operations of the contract.
ConsenSys Diligence (“CD”) typically receives compensation from one or more clients (the “Clients”) for performing the analysis contained in these reports (the “Reports”). The Reports may be distributed through other means, including via ConsenSys publications and other distributions.
The Reports are not an endorsement or indictment of any particular project or team, and the Reports do not guarantee the security of any particular project. This Report does not consider, and should not be interpreted as considering or having any bearing on, the potential economics of a token, token sale or any other product, service or other asset. Cryptographic tokens are emergent technologies and carry with them high levels of technical risk and uncertainty. No Report provides any warranty or representation to any Third-Party in any respect, including regarding the bugfree nature of code, the business model or proprietors of any such business model, and the legal compliance of any such business. No third party should rely on the Reports in any way, including for the purpose of making any decisions to buy or sell any token, product, service or other asset. Specifically, for the avoidance of doubt, this Report does not constitute investment advice, is not intended to be relied upon as investment advice, is not an endorsement of this project or team, and it is not a guarantee as to the absolute security of the project. CD owes no duty to any Third-Party by virtue of publishing these Reports.
PURPOSE OF REPORTS The Reports and the analysis described therein are created solely for Clients and published with their consent. The scope of our review is limited to a review of Solidity code and only the Solidity code we note as being within the scope of our review within this report. The Solidity language itself remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond Solidity that could present security risks. Cryptographic tokens are emergent technologies and carry with them high levels of technical risk and uncertainty.
CD makes the Reports available to parties other than the Clients (i.e., “third parties”) -- on its GitHub account (https://github.com/ConsenSys). CD hopes that by making these analyses publicly available, it can help the blockchain ecosystem develop technical best practices in this rapidly evolving area of innovation.
LINKS TO OTHER WEB SITES FROM THIS WEB SITE You may, through hypertext or other computer links, gain access to web sites operated by persons other than ConsenSys and CD. Such hyperlinks are provided for your reference and convenience only, and are the exclusive responsibility of such web sites' owners. You agree that ConsenSys and CD are not responsible for the content or operation of such Web sites, and that ConsenSys and CD shall have no liability to you or any other person or entity for the use of third party Web sites. Except as described below, a hyperlink from this web Site to another web site does not imply or mean that ConsenSys and CD endorses the content on that Web site or the operator or operations of that site. You are solely responsible for determining the extent to which you may use any content at any other web sites to which you link from the Reports. ConsenSys and CD assumes no responsibility for the use of third-party software on the Web Site and shall have no liability whatsoever to any person or entity for the accuracy or completeness of any outcome generated by such software.
TIMELINESS OF CONTENT The content contained in the Reports is current as of the date appearing on the Report and is subject to change without notice. Unless indicated otherwise, by ConsenSys and CD.