- Truffle 5.0.7
- Ganache-cli 6.3.0 (ganache-core: 2.4.0)
$ npm install
$ npm run chain
$ npm run test
-
This Security Token follows the ERC1400 standard: ethereum/EIPs#1411
-
All ERC1400's methods should be implemented with the exception of those which are meant to be called directly by token holders. The Company is the custodian of investor's private key, subsequently they are not able to call any method where
msg.sender
should correspond to token holder's public address. -
ERC20 methods
transfer
,transferFrom
,approve
andallowance
have not been implemented either.
- The Company.
- Deploys all the contracts and becomes their owner.
- Issuances and Redemptions.
- Rights for removing and attaching documents.
- Authorise/Revoke Controllers and Operators.
- Whitelist/KYC and Blacklist/UnKYC Investors.
- Sets
Rules.sol
contract address to be used by the token. - Can eventually renounce issuance, making impossible to issue tokens again.
- Sets NAV and Lot Size parameters.
- Sets Default Partitions.
- The Company and eventually a third party exchange platform.
- Transfers.
- Have to be authorised for EACH Investor by Issuer.
- Once they are authorised, they are able to transfer ANY token partition on investor's behalf.
- Fund administrator and its managers.
- There should be at least two of them.
- They are able to force Transfers and Redemptions even when lockup periods have not expired yet or lot size rule is not met.
- Every action must include a certificate making use of
bytes controllerData
function param. The certificate is signed off-chain by another controller. - Rights for removing and attaching documents.
- They do not keep their own private keys.
- All their actions (ask for issuance, transfers, redemptions) are recorded off-chain and executed on-chain by Operator or Issuer every 24h.
- Proxy where all methods calls are forwarded via
delegatecall()
to the different ERC Security Token Standard contracts. - Inherits from
ERC1400.sol
contract. - During deployment has to enforce that at least two controllers are set.
- Contains all Security Token state variables.
- Inherits from
Controllable.sol
contract.
- Controllers management.
- A Controller can not be revoked if the total number of controllers goes under 2.
- Makes use of
CertificateController.sol
. - Inherits from
Ownable.sol
contract.
- Sets token owner (Issuer) in its
constructor()
. - Issuer management.
ERC20.sol
,ERC1644.sol
,ERC1643.sol
,ERC1594.sol
andERC1410.sol
.- Implement different standard methods from ERC1400 standard.
- Are called by
SecurityToken.sol
viadelegatecall()
. - Inherit from
ERC1400.sol
, so that they are able to store/read from token's storage and also have access toOwnable.sol
andControllable.sol
methods. - Sometimes they have to communicate with each other via
delegatecall()
. - All Transfer or Redeem methods where partition is not specified, will iterate over
_tokenDefaultPartitions
state variable. - Issue methods where partition is not specified, will take
_tokenDefaultPartitions[0]
as partition to issue.
- Independent contract with its own storage.
- Owned/Related to a single token:
setToken(address tokenContract)
. - It stores 3 main elements:
- Existing token's partitions
- Valid variables for ALL existing partitions.
- Variables values for EACH partition.
- Makes use of unstructured storage, where partition variables values are stored as follows:
bytes32 position = keccak256(abi.encodePacked(partition, key)); assembly { sstore(position, value) }
- Partitions can be added and removed.
- Variables are a struct formed by its key and its kind ('type' word is reserved in Solidity);
{ key: "lockupExpirationRedemptionKey" (bytes32), kind: "uint256" (string) }
- Variables can be added, but not removed.
- Values can be set buy not removed (they have to be set to 0).
- When
totalSupplyByPartition(partition) == 0
,partition
has to be removed and all its values set to 0. - Only related token is allowed to perform this action:
removePartition(bytes32 partition) onlyToken
.
-
Contract without storage called by ERC standard contracts via
call()
. -
Logic-only contract where different rules are applied to make sure Issuances, Transfers, and Redemptions are possible.
-
Communicates via
call()
withSecurityToken.sol
andPartition.sol
to get required state variables values needed to apply the rules. -
Returns ERC1066 standard reason codes + own codes.
Code Reason 0x50 transfer failure 0x51 transfer success 0x52 insufficient balance 0x53 insufficient allowance 0x54 transfers halted (contract paused) 0x55 funds locked (lockup period) 0x56 invalid sender 0x57 invalid receiver 0x58 invalid value 0x59 partition does not exist 0x60 invalid transfer lot size 0x61 invalid issuance (amount) 0x62 invalid issuance (granularity) 0x63 token not issuable
-
Logic-only contract called by
ERC1644.sol
viadelegatecall()
. -
Checks if a Certificate includes correct parameters and if they have been signed by ANOTHER authorised Controller.
-
This Certificate must be included as
bytes controllerData
param every timecontrollerTransferByPartition()
,controllerTransfer()
,controllerRedeemByPartition()
orcontrollerRedeem()
methods are called. -
Current
nonce
value is stored inSecurityToken.sol
making use of unstructured storage.bytes32 constant internal NONCES_MAPPING_POSITION_TOKEN = keccak256("certificate.token.mapping.nonce"); bytes32 position = keccak256(abi.encode(NONCES_MAPPING_POSITION_TOKEN)); assembly { nonce := sload(position) }
-
Certificate generation (off-chain)
- Parameters are ABI encoded forming the certificate:
const certificate = web3.eth.abi.encodeParameters( [ 'bytes32', 'address[]', 'uint256[]' ], [ params.partition, [params.token, params.from, params.to], [params.value, params.nonce] ] );
- Get certificate hash:
const certificateHash = web3.utils.keccak256(certificate);
- Sign certificate hash (ECDSA):
const signedCertificate = await web3.eth.sign( certificateHash, params.controller );
- Get signature parameters:
const signature = getSignatureParameters(signedCertificate);
- ABI encode signature, certificate, and certificate hash:
const controllerData = web3.eth.abi.encodeParameters( [ 'bytes', 'bytes32', {"Signature": { "r": 'bytes32', "s": 'bytes32', "v": 'uint8' }} ], [ certificate, certificateHash, signature ] );
- Parameters are ABI encoded forming the certificate:
-
Certificate validation (on-chain)
-
ABI decode
bytes controllerData
to get certificate, certificate hash and signature:(bytes memory certificate, bytes32 certificateHash, Signature memory signature) = abi.decode( controllerData, (bytes, bytes32, Signature) );
-
ABI decode certificate to get all parameters:
//tokenCert = addressCert[0] //fromCert = addressCert[1] //toCert = addressCert[2] //valueCert = uintCert[0] //nonceCert = uintCert[1] ( bytes32 partitionCert, address[] memory addressCert, uint256[] memory uintCert ) = abi.decode(certificate,(bytes32, address[], uint256[]));
-
Validate decoded values comparing them with method parameters.
-
Read from storage if
nonce
is valid:function _nonce() internal view returns(uint256 nonce) { bytes32 position = keccak256(abi.encode(NONCES_MAPPING_POSITION_TOKEN)); assembly { nonce := sload(position) } }
-
Validate signature (ECDSA recover) and increase the
nonce
:bytes constant internal PREFIX = "\x19Ethereum Signed Message:\n32"; for(uint256 i=0; i < controllers.length; i++) { if(ecrecover(prefixedHash, signature.v, signature.r, signature.s) == controllers[i] && address(msg.sender) != controllers[i]) { _increaseNonce(); return bytes32(0); } }
-
- Same concept than
CertificateToken.sol
. - Every time the Issuer wants to authorise or revoke a Controller, the action has to be signed by a Controller.