pragma solidity 0.5.0; | |
| |
| import "../utils/Pausable.sol"; |
| |
| contract ISecurityToken is Pausable { |
| // ERC-20 |
| |
| uint256 public totalSupply; |
| |
| function balanceOf(address tokenHolder) public view returns (uint256); |
| |
| event Transfer(address indexed from, address indexed to, uint256 amount); |
| |
| // OKTO Security Token |
| |
| string public name; |
| string public symbol; |
| uint8 public decimals; |
| address public whitelistAddress; |
| address[] public operators; |
| bool public released; |
| |
| // Modules handling |
| function addModule(address moduleAddress) public; |
| function removeModule(address moduleAddress) public; |
| function release() public; |
| |
| // Operators handling |
| function authorizeOperator(address operator) public; |
| function revokeOperator(address operator) public; |
| function isOperator(address operator) public view returns (bool); |
| |
| // Tokens handling |
| function balanceOfByTranche(bytes32 tranche, address tokenHolder) public view returns (uint256); |
| function getDestinationTranche(bytes32 sourceTranche, address from, uint256 amount, bytes memory data) public view returns(bytes32); |
| function canTransfer(bytes32 tranche, address operator, address from, address to, uint256 amount, bytes memory data) public view returns (byte, string memory, bytes32); |
| function transferByTranche(bytes32 tranche, address to, uint256 amount, bytes memory data) public returns (bytes32); |
| function operatorTransferByTranche(bytes32 tranche, address from, address to, uint256 amount, bytes memory data) public returns (bytes32); |
| function tranchesOf(address tokenHolder) public view returns (bytes32[] memory); |
| function issueByTranche(bytes32 tranche, address tokenHolder, uint256 amount, bytes memory data) public; |
| function burnByTranche(bytes32 tranche, address tokenHolder, uint256 amount, bytes memory data) public; |
| |
| // Events |
| event AddedModule(address moduleAddress, string moduleType); |
| event RemovedModule(address moduleAddress); |
| event Released(); |
| event AuthorizedOperator(address indexed operator); |
| event RevokedOperator(address indexed operator); |
| event TransferByTranche(bytes32 fromTranche, bytes32 toTranche, address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data); |
pragma solidity ^0.5.0; | |
| |
| import "../utils/Factory.sol"; |
| import "../tokens/TokenModule.sol"; |
| import "./Module.sol"; |
| |
| contract ForcedTransferTokenModule is TransferValidatorTokenModule,TransferListenerTokenModule,TokenModule { |
| struct ForcedTransfer { |
| bytes32 fromTranche; |
| bytes32 toTranche; |
| address operator; |
| address from; |
| address to; |
| uint256 amount; |
| } |
| |
| mapping(bytes32 => ForcedTransfer) pendingForceTransfers; |
| uint256 numberOfPendingTransfers; |
| |
| constructor(address _tokenAddress) |
| TokenModule(_tokenAddress, "forcedTransfer") |
| public |
| { |
| } |
| |
| function getFeatures() |
| public view returns(Module.Feature[] memory) |
| { |
| Module.Feature[] memory features = new Module.Feature[](2); |
| features[0] = Module.Feature.TransferValidator; |
| features[1] = Module.Feature.TransferListener; |
| return features; |
| } |
| |
| function approveForcedTransfer(bytes32 fromTranche, bytes32 toTranche, address operator, address from, address to, uint256 amount) |
| onlyTokenOwner |
| public |
| { |
| require(from != address(0), "Invalid source address"); |
| require(to != address(0), "Invalid destination address"); |
| require(operator != address(0), "Invalid operator"); |
| require(amount >= 0, "Negative amount"); |
| |
| bytes memory hashBytes = abi.encodePacked(fromTranche, toTranche, operator, from, to, amount); |
| bytes32 hash = keccak256(hashBytes); |
| if (pendingForceTransfers[hash].from == address(0)) { |
| pendingForceTransfers[hash].fromTranche = fromTranche; |
| pendingForceTransfers[hash].toTranche = toTranche; |
| pendingForceTransfers[hash].operator = operator; |
| pendingForceTransfers[hash].from = from; |
| pendingForceTransfers[hash].to = to; |
| pendingForceTransfers[hash].amount = amount; |
| numberOfPendingTransfers++; |
| emit ApprovedForcedTransfer(fromTranche, toTranche, operator, from, to, amount, hash); |
| } |
| } |
| |
| function revokeForcedTransfer(bytes32 fromTranche, bytes32 toTranche, address operator, address from, address to, uint256 amount) |
| onlyTokenOwner |
| public |
| { |
| require(from != address(0), "Invalid source address"); |
| require(to != address(0), "Invalid destination address"); |
| require(operator != address(0), "Invalid operator"); |
| require(amount >= 0, "Negative amount"); |
| |
| bytes memory hashBytes = abi.encodePacked(fromTranche, toTranche, operator, from, to, amount); |
| bytes32 hash = keccak256(hashBytes); |
| if (pendingForceTransfers[hash].from != address(0)) { |
| delete pendingForceTransfers[hash]; |
| numberOfPendingTransfers--; |
| emit RevokedForcedTransfer(fromTranche, toTranche, operator, from, to, amount, hash); |
| } |
| } |
| |
| function validateTransfer(bytes32 fromTranche, bytes32 toTranche, address operator, address from, address to, uint256 amount, bytes memory) |
| public view returns (byte, string memory) |
| { |
| if (numberOfPendingTransfers > 0) { |
| bytes memory hashBytes = abi.encodePacked(fromTranche, toTranche, operator, from, to, amount); |
| bytes32 hash = keccak256(hashBytes); |
| ForcedTransfer storage forcedTransfer = pendingForceTransfers[hash]; |
| // we check one attribute to see if it exists |
| if (forcedTransfer.from == from) { |
| return (0xAF, "Forced transfer"); |
| } |
| } |
| // if it is not forced, we still returned approved |
| return (0xA1, "Approved"); |
| } |
| |
| function transferDone(bytes32 fromTranche, bytes32 toTranche, address operator, address from, address to, uint256 amount, bytes memory) |
| onlyToken |
| public |
| { |
| if (numberOfPendingTransfers == 0) { |
| // no need to check this |
| return; |
| } |
| bytes memory hashBytes = abi.encodePacked(fromTranche, toTranche, operator, from, to, amount); |
| bytes32 hash = keccak256(hashBytes); |
| if (pendingForceTransfers[hash].from != address(0)) { |
| emit ExecutedForcedTransfer(fromTranche, toTranche, operator, from, to, amount, hash); |
| delete pendingForceTransfers[hash]; |
| numberOfPendingTransfers--; |
| } |
| } |
| |
| event ApprovedForcedTransfer(bytes32 fromTranche, bytes32 toTranche, address indexed operator, address indexed from, address to, uint256 amount, bytes32 hash); |
| event RevokedForcedTransfer(bytes32 fromTranche, bytes32 toTranche, address indexed operator, address indexed from, address to, uint256 amount, bytes32 hash); |
| event ExecutedForcedTransfer(bytes32 fromTranche, bytes32 toTranche, address indexed operator, address indexed from, address to, uint256 amount, bytes32 hash); |
pragma solidity ^0.5.0; | |
| |
| import "../utils/SafeMath.sol"; |
| import "../utils/Factory.sol"; |
| import "../whitelists/IWhitelist.sol"; |
| import "../whitelists/WhitelistModule.sol"; |
| import "../tokens/ISecurityToken.sol"; |
| import "../tokens/TokenModule.sol"; |
| import "./Module.sol"; |
| |
| contract InvestorsLimitTokenModule is TransferValidatorTokenModule,TransferListenerTokenModule,TokenModule,WhitelistModule { |
| using SafeMath for uint256; |
| |
| bytes32 constant INVESTOR_ID_PROP = bytes32("investorId"); |
| |
| uint256 public limit; |
| bool public checkInvestorId; |
| |
| uint256 public numberOfInvestors; |
| bytes32 investorIdProperty; |
| mapping(bytes32 => uint256) balancePerInvestor; |
| |
| constructor(address _tokenAddress, address _whitelistAddress, uint256 _limit, bool _checkInvestorId) |
| TokenModule(_tokenAddress, "investorsLimit") |
| WhitelistModule(_whitelistAddress, "investorsLimit") |
| public |
| { |
| require(_limit > 0, "Limit must be greater than zero"); |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| require(token.whitelistAddress() == _whitelistAddress, "Whitelist must be the same as the whitelist in the token"); |
| |
| limit = _limit; |
| checkInvestorId = _checkInvestorId; |
| } |
| |
| function getFeatures() |
| public view returns(Module.Feature[] memory) |
| { |
| Module.Feature[] memory features = new Module.Feature[](3); |
| features[0] = Module.Feature.TransferValidator; |
| features[1] = Module.Feature.TransferListener; |
| features[2] = Module.Feature.WhitelistListener; |
| return features; |
| } |
| |
| function validateTransfer(bytes32, bytes32, address, address from, address to, uint256 amount, bytes memory) |
| public view returns (byte, string memory) |
| { |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| uint256 diff; |
| if (checkInvestorId) { |
| // validate using balance per investor |
| IWhitelist whitelist = IWhitelist(whitelistAddress); |
| bytes32 fromInvestorId; |
| bytes32 toInvestorId; |
| if (from != address(0)) { |
| fromInvestorId = whitelist.getProperty(from, INVESTOR_ID_PROP); |
| } |
| if (to != address(0)) { |
| toInvestorId = whitelist.getProperty(to, INVESTOR_ID_PROP); |
| } |
| if (to != address(0) && balancePerInvestor[toInvestorId] == 0) { |
| // if the sender is transferring all its tokens, then we can assume there will be one investor less |
| diff = (from != address(0) && balancePerInvestor[fromInvestorId] == amount) ? 1 : 0; |
| // this is a new investor so we need to check limit |
| if ((numberOfInvestors - diff) >= limit) { |
| return (0xA8, "Maximum number of investors reached"); |
| } |
| } |
| } else { |
| if (to != address(0) && token.balanceOf(to) == 0) { |
| // if the sender is transferring all its tokens, then we can assume there will be one investor less |
| diff = (from != address(0) && token.balanceOf(from) == amount) ? 1 : 0; |
| // this is a new investor so we need to check limit |
| if ((numberOfInvestors - diff) >= limit) { |
| return (0xA8, "Maximum number of investors reached"); |
| } |
| } |
| } |
| return (0xA1, "Approved"); |
| } |
| |
| function transferDone(bytes32, bytes32, address, address from, address to, uint256 amount, bytes memory) |
| onlyToken |
| public |
| { |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| if (checkInvestorId) { |
| // if there is a whitelist we should take into account balancer per investor instead of per wallet |
| IWhitelist whitelist = IWhitelist(whitelistAddress); |
| bytes32 fromInvestorId; |
| bytes32 toInvestorId; |
| if (from != address(0)) { |
| fromInvestorId = whitelist.getProperty(from, INVESTOR_ID_PROP); |
| balancePerInvestor[fromInvestorId] = balancePerInvestor[fromInvestorId].sub(amount); |
| } |
| if (to != address(0)) { |
| toInvestorId = whitelist.getProperty(to, INVESTOR_ID_PROP); |
| balancePerInvestor[toInvestorId] = balancePerInvestor[toInvestorId].add(amount); |
| } |
| if (to != address(0) && balancePerInvestor[toInvestorId] == amount) { |
| // it means that this is a new investor as all the tokens are the ones that were transferred in this operation |
| numberOfInvestors++; |
| } |
| if (from != address(0) && balancePerInvestor[fromInvestorId] == 0) { |
| // decrease the number of investors as the sender does not have any tokens after the transaction |
| numberOfInvestors--; |
| } |
| } else { |
| if (to != address(0) && token.balanceOf(to) == amount) { |
| // it means that this is a new investor as all the tokens are the ones that were transferred in this operation |
| numberOfInvestors++; |
| } |
| if (from != address(0) && token.balanceOf(from) == 0) { |
| // decrease the number of investors as the sender does not have any tokens after the transaction |
| numberOfInvestors--; |
| } |
| } |
| } |
| |
| function investorUpdated(address investor, bytes32 bucket, bytes32 newValue, bytes32 oldValue) |
| onlyWhitelist |
| public |
| { |
| if (bucket == INVESTOR_ID_PROP && newValue != oldValue) { |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| uint256 balanceOfAddress = token.balanceOf(investor); |
| // move balance of the address to the new investor |
| balancePerInvestor[oldValue] = balancePerInvestor[oldValue].sub(balanceOfAddress); |
| balancePerInvestor[newValue] = balancePerInvestor[newValue].add(balanceOfAddress); |
| if (balancePerInvestor[newValue] == balanceOfAddress) { |
| // it means that this is a new investor as all the tokens are the ones that were moved in this operation |
| numberOfInvestors++; |
| } |
| if (balancePerInvestor[oldValue] == 0) { |
| // decrease the number of investors as the old investor does not have any tokens after the operation |
| numberOfInvestors--; |
| } |
| if (numberOfInvestors > limit) { |
| revert("Maximum number of investors reached"); |
| } |
| } |
| } |
| } |
| |
| contract InvestorsLimitTokenModuleFactory is Factory { |
| function createInstance(address tokenAddress, uint256 limit, bool checkInvestorId) |
| public returns(address) |
| { |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| address whitelistAddress = token.whitelistAddress(); |
| InvestorsLimitTokenModule instance = new InvestorsLimitTokenModule(tokenAddress, whitelistAddress, limit, checkInvestorId); |
| instance.transferOwnership(msg.sender); |
| addInstance(address(instance)); |
| return address(instance); |
| } |
| } |
pragma solidity ^0.5.0; | |
| |
| import "../whitelists/IWhitelist.sol"; |
| import "../utils/Factory.sol"; |
| import "../tokens/TokenModule.sol"; |
| import "./Module.sol"; |
| |
| contract KycTokenModule is TransferValidatorTokenModule,TokenModule { |
| address public whitelistAddress; |
| |
| bytes32 constant KYC_PROP = bytes32("kycStatus"); |
| |
| constructor(address _tokenAddress) |
| TokenModule(_tokenAddress, "kyc") |
| public |
| { |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| |
| whitelistAddress = token.whitelistAddress(); |
| |
| } |
| |
| function getFeatures() |
| public view returns(Module.Feature[] memory) |
| { |
| Module.Feature[] memory features = new Module.Feature[](1); |
| features[0] = Module.Feature.TransferValidator; |
| return features; |
| } |
| |
| |
| function validateTransfer(bytes32, bytes32, address, address, address to, uint256, bytes memory) |
| public view returns (byte, string memory) |
| { |
| IWhitelist whitelist = IWhitelist(whitelistAddress); |
| uint256 propValue = uint256(whitelist.getProperty(to, KYC_PROP)); |
| if (propValue == 1 || propValue == 2) { |
| return (0xA1, "Approved"); |
| } else { |
| return (0xA6, "Receiver not in whitelist"); |
| } |
| } |
| } |
| |
| contract KycTokenModuleFactory is Factory { |
| function createInstance(address tokenAddress) |
| public returns(address) |
| { |
| KycTokenModule instance = new KycTokenModule(tokenAddress); |
| instance.transferOwnership(msg.sender); |
| addInstance(address(instance)); |
| return address(instance); |
| } |
| } |
pragma solidity ^0.5.0; | |
| |
| import "../utils/Factory.sol"; |
| import "../utils/Pausable.sol"; |
| import "../tokens/TokenModule.sol"; |
| import "./Module.sol"; |
| |
| contract OfferingTokenModule is TransferValidatorTokenModule,TokenModule,Pausable { |
| uint256 public start; |
| uint256 public end; |
| |
| constructor(address _tokenAddress, uint256 _start, uint256 _end) |
| TokenModule(_tokenAddress, "offering") |
| public |
| { |
| start = _start; |
| end = _end; |
| } |
| |
| function getFeatures() |
| public view returns(Module.Feature[] memory) |
| { |
| Module.Feature[] memory features = new Module.Feature[](1); |
| features[0] = Module.Feature.TransferValidator; |
| return features; |
| } |
| |
| function issueTokens(bytes32[] memory tranches, address[] memory investors, uint256[] memory amounts) |
| onlyTokenOperator whenNotPaused |
| public |
| { |
| require(investors.length == tranches.length && tranches.length == amounts.length, "Number of investors, tranches and amounts does not match"); |
| require(investors.length > 0, "Tokens for at least one investor should be issued"); |
| require(now >= start, "The offering has not started yet"); |
| require(now <= end, "The offering has finished already"); |
| byte res; |
| string memory message; |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| for (uint i = 0; i < investors.length; i++) { |
| (res, message, ) = token.canTransfer(tranches[i], msg.sender, address(0), investors[i], amounts[i], abi.encodePacked("issuing")); |
| if (res != 0xA0 && res != 0xA1 && res != 0xA2 && res != 0xAF) { |
| emit TokenAllocationError(i, res, message); |
| } else { |
| token.issueByTranche(tranches[i], investors[i], amounts[i], abi.encodePacked("issuing")); |
| } |
| } |
| } |
| |
| function reserveTokens(bytes32[] memory tranches, address[] memory investors, uint256[] memory amounts) |
| onlyTokenOwner whenNotPaused |
| public |
| { |
| require(investors.length == tranches.length && tranches.length == amounts.length, "Number of investors, tranches and amounts does not match"); |
| require(investors.length > 0, "Tokens for at least one investor should be issued"); |
| require(now < start, "The offering has started already"); |
| byte res; |
| string memory message; |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| for (uint i = 0; i < investors.length; i++) { |
| (res, message, ) = token.canTransfer(tranches[i], msg.sender, address(0), investors[i], amounts[i], abi.encodePacked("reservation")); |
| if (res != 0xA0 && res != 0xA1 && res != 0xA2 && res != 0xAF) { |
| emit TokenAllocationError(i, res, message); |
| } else { |
| token.issueByTranche(tranches[i], investors[i], amounts[i], abi.encodePacked("reservation")); |
| } |
| } |
| } |
| |
| function validateTransfer(bytes32, bytes32, address, address from, address, uint256, bytes memory data) |
| public view returns (byte, string memory) |
| { |
| if (from == address(0)) { |
| // if this is a token reservation (only done by the owner of the token) we don't perform this validation |
| if (keccak256(data) != keccak256(abi.encodePacked("reservation"))) { |
| // we need to only allow issuance if we are between start and end |
| if (now < start || now > end) { |
| return (0xA8, "Offering not in progress"); |
| } else if (paused) { |
| // if offering is in progress, but it is paused we will return an error |
| return (0xA8, "Offering is paused"); |
| } |
| } else { |
| // if it is a reservation, but it is after the offering started, return an error |
| if (now >= start) { |
| return (0xA8, "Offering already started"); |
| } |
| } |
| } else { |
| // if this is a regular transfer and not issuance, we will reject it if the offering is not finished |
| if (now <= end) { |
| return (0xA8, "Transfers are not allowed until offering is finished"); |
| } |
| } |
| return (0xA1, "Approved"); |
| } |
| |
| event TokenAllocationError(uint256 index, byte code, string errorMessage); |
| } |
| |
| contract OfferingTokenModuleFactory is Factory { |
| function createInstance(address tokenAddress, uint256 start, uint256 end) |
| public returns(address) |
| { |
| OfferingTokenModule instance = new OfferingTokenModule(tokenAddress, start, end); |
| instance.transferOwnership(msg.sender); |
| addInstance(address(instance)); |
| return address(instance); |
| } |
| } |
pragma solidity ^0.5.0; | |
| |
| import "../whitelists/Whitelist.sol"; |
| import "../utils/Factory.sol"; |
| import "../tokens/TokenModule.sol"; |
| import "./Module.sol"; |
| |
| contract RestrictSenderTokenModule is TransferValidatorTokenModule,TokenModule { |
| address public whitelistAddress; |
| bool public allowOperators; |
| bool public allowAts; |
| |
| bytes32 constant ATS_PROP = bytes32("ats"); |
| |
| constructor(address _tokenAddress, bool _allowOperators, bool _allowAts) |
| TokenModule(_tokenAddress, "restrictSender") |
| public |
| { |
| require(_allowOperators || _allowAts, "You need to allow at least one type of users"); |
| |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| whitelistAddress = token.whitelistAddress(); |
| allowOperators = _allowOperators; |
| allowAts = _allowAts; |
| } |
| |
| function getFeatures() |
| public view returns(Module.Feature[] memory) |
| { |
| Module.Feature[] memory features = new Module.Feature[](1); |
| features[0] = Module.Feature.TransferValidator; |
| return features; |
| } |
| |
| |
| function validateTransfer(bytes32, bytes32, address operator, address from, address, uint256, bytes memory) |
| public view returns (byte, string memory) |
| { |
| IWhitelist whitelist = IWhitelist(whitelistAddress); |
| if ( |
| allowOperators && operator != address(0) || |
| allowAts && operator == address(0) && whitelist.getProperty(from, ATS_PROP) == bytes32(uint256(1)) |
| ) { |
| return (0xA1, "Approved"); |
| } else { |
| return (0xA5, "Sender is restricted"); |
| } |
| } |
| } |
| |
| contract RestrictSenderTokenModuleFactory is Factory { |
| function createInstance(address tokenAddress, bool allowOperators, bool allowAts) |
| public returns(address) |
| { |
| RestrictSenderTokenModule instance = new RestrictSenderTokenModule(tokenAddress, allowOperators, allowAts); |
pragma solidity ^0.5.0; | |
| |
| import "../utils/Factory.sol"; |
| import "../tokens/ISecurityToken.sol"; |
| import "../tokens/TokenModule.sol"; |
| import "./Module.sol"; |
| |
| contract SupplyLimitTokenModule is TransferValidatorTokenModule,TokenModule { |
| uint256 public limit; |
| |
| constructor(address _tokenAddress, uint256 _limit) |
| TokenModule(_tokenAddress, "supplyLimit") |
| public |
| { |
| limit = _limit; |
| } |
| |
| function getFeatures() |
| public view returns(Module.Feature[] memory) |
| { |
| Module.Feature[] memory features = new Module.Feature[](1); |
| features[0] = Module.Feature.TransferValidator; |
| return features; |
| } |
| |
| |
| function validateTransfer(bytes32, bytes32, address, address from, address, uint256 amount, bytes memory) |
| public view returns (byte, string memory) |
| { |
| if (from == address(0)) { |
| // this is an issuance of tokens |
| ISecurityToken token = ISecurityToken(tokenAddress); |
| if ((token.totalSupply() + amount) > limit) { |
| return (0xA8, "Supply limit reached"); |
| } |
| } |
| return (0xA1, "Approved"); |
| } |
| } |
| |
| contract SupplyLimitTokenModuleFactory is Factory { |
| function createInstance(address tokenAddress, uint256 limit) |
| public returns(address) |
| { |
| SupplyLimitTokenModule instance = new SupplyLimitTokenModule(tokenAddress, limit); |
| instance.transferOwnership(msg.sender); |
| addInstance(address(instance)); |
| return address(instance); |
| } |
| } |
pragma solidity ^0.5.0; | |
| |
| import "../utils/Factory.sol"; |
| |
| |
| // This multisig wallet was develop by Gnosis: https://github.com/gnosis/MultiSigWallet |
| |
| /// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. |
| /// @author Stefan George - <stefan.george@consensys.net> |
| contract MultiSigWallet { |
| |
| /* |
| * Events |
| */ |
| event Confirmation(address indexed sender, uint indexed transactionId); |
| event Revocation(address indexed sender, uint indexed transactionId); |
| event Submission(uint indexed transactionId); |
| event Execution(uint indexed transactionId); |
| event ExecutionFailure(uint indexed transactionId); |
| event Deposit(address indexed sender, uint value); |
| event OwnerAddition(address indexed owner); |
| event OwnerRemoval(address indexed owner); |
| event RequirementChange(uint required); |
| |
| /* |
| require(!isOwner[owner]); |
| _; |
| } |
| |
| modifier ownerExists(address owner) { |
| require(isOwner[owner]); |
| _; |
| } |
| |
| modifier transactionExists(uint transactionId) { |
| require(transactions[transactionId].destination != address(0)); |
| _; |
| } |
| |
| modifier confirmed(uint transactionId, address owner) { |
| require(confirmations[transactionId][owner]); |
| _; |
| } |
| |
| modifier notConfirmed(uint transactionId, address owner) { |
| require(!confirmations[transactionId][owner]); |
| _; |
| } |
| |
| modifier notExecuted(uint transactionId) { |
| require(!transactions[transactionId].executed); |
| _; |
| } |
| |
| modifier notNull(address _address) { |
| require(_address != address(0)); |
| _; |
| } |
| |
| modifier validRequirement(uint ownerCount, uint _required) { |
| require(ownerCount <= MAX_OWNER_COUNT |
| && _required <= ownerCount |
| && _required != 0 |
| && ownerCount != 0); |
| _; |
| } |
| |
| /// @dev Fallback function allows to deposit ether. |
| function() |
| external payable |
| { |
| if (msg.value > 0) |
| emit Deposit(msg.sender, msg.value); |
| } |
| |
| /* |
| * Public functions |
| */ |
| /// @dev Contract constructor sets initial owners and required number of confirmations. |
| /// @param _owners List of initial owners. |
| /// @param _required Number of required confirmations. |
| constructor(address[] memory _owners, uint _required) |
| public |
| validRequirement(_owners.length, _required) |
| { |
| for (uint i=0; i<_owners.length; i++) { |
| require(!isOwner[_owners[i]] && _owners[i] != address(0)); |
| isOwner[_owners[i]] = true; |
| } |
| owners = _owners; |
| required = _required; |
| } |
| |
| /// @dev Allows to add a new owner. Transaction has to be sent by wallet. |
| /// @param owner Address of new owner. |
| function addOwner(address owner) |
| public |
| onlyWallet |
| ownerDoesNotExist(owner) |
| notNull(owner) |
| validRequirement(owners.length + 1, required) |
| { |
| isOwner[owner] = true; |
| owners.push(owner); |
| emit OwnerAddition(owner); |
| } |
| |
| /// @dev Allows to remove an owner. Transaction has to be sent by wallet. |
| /// @param owner Address of owner. |
| function removeOwner(address owner) |
| public |
| onlyWallet |
| ownerExists(owner) |
| { |
| isOwner[owner] = false; |
| for (uint i=0; i<owners.length - 1; i++) |
| if (owners[i] == owner) { |
| owners[i] = owners[owners.length - 1]; |
| break; |
| } |
| owners.length -= 1; |
| if (required > owners.length) |
| changeRequirement(owners.length); |
| emit OwnerRemoval(owner); |
| } |
| |
| /// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet. |
| /// @param owner Address of owner to be replaced. |
| /// @param newOwner Address of new owner. |
| function replaceOwner(address owner, address newOwner) |
| public |
| onlyWallet |
| ownerExists(owner) |
| ownerDoesNotExist(newOwner) |
| { |
| for (uint i=0; i<owners.length; i++) |
| if (owners[i] == owner) { |
| owners[i] = newOwner; |
| break; |
| } |
| isOwner[owner] = false; |
| isOwner[newOwner] = true; |
| emit OwnerRemoval(owner); |
| emit OwnerAddition(newOwner); |
| } |
| |
| /// @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet. |
| /// @param _required Number of required confirmations. |
| function changeRequirement(uint _required) |
| public |
| onlyWallet |
| validRequirement(owners.length, _required) |
| { |
| required = _required; |
| emit RequirementChange(_required); |
| } |
| |
| /// @dev Allows an owner to submit and confirm a transaction. |
| /// @param destination Transaction target address. |
| /// @param value Transaction ether value. |
| /// @param data Transaction data payload. |
| /// @return Returns transaction ID. |
| function submitTransaction(address destination, uint value, bytes memory data) |
| public |
| returns (uint transactionId) |
| { |
| transactionId = addTransaction(destination, value, data); |
| confirmTransaction(transactionId); |
| } |
| |
| /// @dev Allows an owner to confirm a transaction. |
| /// @param transactionId Transaction ID. |
| function confirmTransaction(uint transactionId) |
| public |
| ownerExists(msg.sender) |
| transactionExists(transactionId) |
| notConfirmed(transactionId, msg.sender) |
| { |
| confirmations[transactionId][msg.sender] = true; |
| emit Confirmation(msg.sender, transactionId); |
| executeTransaction(transactionId); |
| } |
| |
| /// @dev Allows an owner to revoke a confirmation for a transaction. |
| /// @param transactionId Transaction ID. |
| function revokeConfirmation(uint transactionId) |
| public |
| ownerExists(msg.sender) |
| confirmed(transactionId, msg.sender) |
| notExecuted(transactionId) |
| { |
| confirmations[transactionId][msg.sender] = false; |
| emit Revocation(msg.sender, transactionId); |
| } |
| |
| /// @dev Allows anyone to execute a confirmed transaction. |
| /// @param transactionId Transaction ID. |
| function executeTransaction(uint transactionId) |
| public |
| ownerExists(msg.sender) |
| confirmed(transactionId, msg.sender) |
| notExecuted(transactionId) |
| { |
| if (isConfirmed(transactionId)) { |
| Transaction storage txn = transactions[transactionId]; |
| txn.executed = true; |
| if (external_call(txn.destination, txn.value, txn.data.length, txn.data)) |
| emit Execution(transactionId); |
| else { |
| emit ExecutionFailure(transactionId); |
| txn.executed = false; |
| } |
| } |
| } |
| |
| // call has been separated into its own function in order to take advantage |
| // of the Solidity's code generator to produce a loop that copies tx.data into memory. |
| function external_call(address destination, uint value, uint dataLength, bytes memory data) private returns (bool) { |
| bool result; |
| assembly { |
| let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention) |
| let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that |
| result := call( |
| sub(gas, 34710), // 34710 is the value that solidity is currently emitting |
| // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) + |
| // callNewAccountGas (25000, in case the destination address does not exist and needs creating) |
| destination, |
| value, |
| d, |
| dataLength, // Size of the input (in bytes) - this is what fixes the padding problem |
| x, |
| 0 // Output is ignored, therefore the output size is zero |
| ) |
| } |
| return result; |
| } |
| |
| /// @dev Returns the confirmation status of a transaction. |
| /// @param transactionId Transaction ID. |
| /// @return Confirmation status. |
| function isConfirmed(uint transactionId) |
| public |
| view |
| returns (bool) |
| { |
| uint count = 0; |
| for (uint i=0; i<owners.length; i++) { |
| if (confirmations[transactionId][owners[i]]) |
| count += 1; |
| if (count == required) |
| return true; |
| } |
| } |
| |
| /* |
| * Internal functions |
| */ |
| /// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet. |
| /// @param destination Transaction target address. |
| /// @param value Transaction ether value. |
| /// @param data Transaction data payload. |
| /// @return Returns transaction ID. |
| function addTransaction(address destination, uint value, bytes memory data) |
| internal |
| notNull(destination) |
| returns (uint transactionId) |
| { |
| transactionId = transactionCount; |
| transactions[transactionId] = Transaction({ |
| destination: destination, |
| value: value, |
| data: data, |
| executed: false |
| }); |
| transactionCount += 1; |
| emit Submission(transactionId); |
| } |
| |
| /* |
| * Web3 call functions |
| */ |
| /// @dev Returns number of confirmations of a transaction. |
| /// @param transactionId Transaction ID. |
| /// @return Number of confirmations. |
| function getConfirmationCount(uint transactionId) |
| public |
| view |
| returns (uint count) |
| { |
| for (uint i=0; i<owners.length; i++) |
| if (confirmations[transactionId][owners[i]]) |
| count += 1; |
| } |
| |
| /// @dev Returns total number of transactions after filers are applied. |
| /// @param pending Include pending transactions. |
| /// @param executed Include executed transactions. |
| /// @return Total number of transactions after filters are applied. |
| function getTransactionCount(bool pending, bool executed) |
| public |
| view |
| returns (uint count) |
| { |
| for (uint i=0; i<transactionCount; i++) |
| if ( pending && !transactions[i].executed |
| || executed && transactions[i].executed) |
| count += 1; |
| } |
| |
| /// @dev Returns list of owners. |
| /// @return List of owner addresses. |
| function getOwners() |
| public |
| view |
| returns (address[] memory) |
| { |
| return owners; |
| } |
| |
| /// @dev Returns array with owner addresses, which confirmed transaction. |
| /// @param transactionId Transaction ID. |
| /// @return Returns array of owner addresses. |
| function getConfirmations(uint transactionId) |
| public |
| view |
| returns (address[] memory _confirmations) |
| { |
| address[] memory confirmationsTemp = new address[](owners.length); |
| uint count = 0; |
| uint i; |
| for (i=0; i<owners.length; i++) |
| if (confirmations[transactionId][owners[i]]) { |
| confirmationsTemp[count] = owners[i]; |
| count += 1; |
| } |
| _confirmations = new address[](count); |
| for (i=0; i<count; i++) |
| _confirmations[i] = confirmationsTemp[i]; |
| } |
| |
| /// @dev Returns list of transaction IDs in defined range. |
| /// @param from Index start position of transaction array. |
| /// @param to Index end position of transaction array. |
| /// @param pending Include pending transactions. |
| /// @param executed Include executed transactions. |
| /// @return Returns array of transaction IDs. |
| function getTransactionIds(uint from, uint to, bool pending, bool executed) |
| public |
| view |
| returns (uint[] memory _transactionIds) |
| { |
| uint[] memory transactionIdsTemp = new uint[](transactionCount); |
| uint count = 0; |
| uint i; |
| for (i=0; i<transactionCount; i++) |
| if ( pending && !transactions[i].executed |
| || executed && transactions[i].executed) |
| { |
| transactionIdsTemp[count] = i; |
| count += 1; |
| } |
| _transactionIds = new uint[](to - from); |
| for (i=from; i<to; i++) |
| _transactionIds[i - from] = transactionIdsTemp[i]; |
| } |
| } |
| |
| contract MultiSigWalletFactory is Factory { |
| function createInstance(address[] memory _owners, uint _required) |
| public returns(address) |
| { |
| MultiSigWallet instance = new MultiSigWallet(_owners, _required); |
| addInstance(address(instance)); |
| return address(instance); |
| } |
| } |
pragma solidity ^0.5.0; | |
| |
| import "../utils/Ownable.sol"; |
| import "../utils/Factory.sol"; |
| import "../utils/AddressArrayLib.sol"; |
| import "./WhitelistModule.sol"; |
| |
| contract IWhitelist is Ownable { |
| address[] public validators; |
| |
| function addValidator(address validator) public; |
| function removeValidator(address validator) public; |
| function isValidator(address validator) public view returns(bool); |
| function setBucket(address investor, bytes32 bucket, bytes32 value) public; |
| function setManyBuckets(address[] memory investors, bytes32[] memory buckets, bytes32[] memory values) public; |
| function getBucket(address investor, bytes32 bucket) public view returns(bytes32); |
| function getProperty(address investor, bytes32 property) public view returns(bytes32); |
| function addProperty(bytes32 code, bytes32 bucket, uint8 from, uint16 len) public; |
| function addModule(address moduleAddress) public; |
| function removeModule(address moduleAddress) public; |
| function isModule(address moduleAddress) public view returns (bool); |
| |
| event AddedValidator(address validator); |
| event RemovedValidator(address validator); |
| event AddedProperty(bytes32 code, bytes32 bucket, uint8 from, uint16 len); |
pragma solidity ^0.5.0; | |
| |
| import "../utils/Ownable.sol"; |
| import "../utils/Factory.sol"; |
| import "./Whitelist.sol"; |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Standard properties: |
| // |
| // General Bucket ---------------------------------------------- |
| // KYC status index 0, length 2 bits (00: pending, 01: auto-approved, 10: manually-approved, 11: disapproved) |
| // KYC status updated index 2, length 40 bits |
| // AML status index 42, length 2 bits (00: pending, 01: auto-approved, 10: manually-approved, 11: disapproved) |
| // AML status updated index 44, length 40 bits |
| // Accredited status index 84, length 3 bits (000: pending, 001: auto-approved, 010: manually-approved, 011: self-approved, 100: disapproved) |
| // Accredited status updated index 87, length 40 bits |
| // Country code (two letters code) index 127, length 16 bits (two letters ascii code lower case) |
| // Insider index 143, length 1 bits |
| // Lockup expiration index 144, length 40 bits |
| // ATS index 184, length 1 bits |
| // Blocked index 185, length 1 bits |
| // Investor ID Bucket ------------------------------------------ |
| // Investor ID index 0, length 256 bits |
| // KYC Reference Bucket ---------------------------------------- |
| // KYC Reference index 0, length 256 bits |
| // AML Reference Bucket ---------------------------------------- |
| // AML Reference index 0, length 256 bits |
| // Accredited Reference Bucket --------------------------------- |
| // Accredited Reference index 0, length 256 bits |
| // |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| contract StandardWhitelist is Whitelist { |
| constructor(address[] memory validators, bytes32[] memory codes, bytes32[] memory buckets, uint8[] memory froms, uint16[] memory lens) |
| Whitelist(validators, codes, buckets, froms, lens) |
| public |
| { |
| // define this standard properties; if they were also passed in the constructor |
| // they will be overridden |
| |
| propertiesDefinition[bytes32("kycStatus")].code = bytes32("kycStatus"); |
| propertiesDefinition[bytes32("kycStatus")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("kycStatus")].from = 0; |
| propertiesDefinition[bytes32("kycStatus")].len = 2; |
| |
| propertiesDefinition[bytes32("kycStatusUpdated")].code = bytes32("kycStatusUpdated"); |
| propertiesDefinition[bytes32("kycStatusUpdated")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("kycStatusUpdated")].from = 2; |
| propertiesDefinition[bytes32("kycStatusUpdated")].len = 40; |
| |
| propertiesDefinition[bytes32("amlStatus")].code = bytes32("amlStatus"); |
| propertiesDefinition[bytes32("amlStatus")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("amlStatus")].from = 42; |
| propertiesDefinition[bytes32("amlStatus")].len = 2; |
| |
| propertiesDefinition[bytes32("amlStatusUpdated")].code = bytes32("amlStatusUpdated"); |
| propertiesDefinition[bytes32("amlStatusUpdated")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("amlStatusUpdated")].from = 44; |
| propertiesDefinition[bytes32("amlStatusUpdated")].len = 40; |
| |
| propertiesDefinition[bytes32("accreditedStatus")].code = bytes32("accreditedStatus"); |
| propertiesDefinition[bytes32("accreditedStatus")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("accreditedStatus")].from = 84; |
| propertiesDefinition[bytes32("accreditedStatus")].len = 3; |
| |
| propertiesDefinition[bytes32("accreditedStatusUpdated")].code = bytes32("accreditedStatusUpdated"); |
| propertiesDefinition[bytes32("accreditedStatusUpdated")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("accreditedStatusUpdated")].from = 87; |
| propertiesDefinition[bytes32("accreditedStatusUpdated")].len = 40; |
| |
| propertiesDefinition[bytes32("country")].code = bytes32("country"); |
| propertiesDefinition[bytes32("country")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("country")].from = 127; |
| propertiesDefinition[bytes32("country")].len = 16; |
| |
| propertiesDefinition[bytes32("insider")].code = bytes32("insider"); |
| propertiesDefinition[bytes32("insider")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("insider")].from = 143; |
| propertiesDefinition[bytes32("insider")].len = 1; |
| |
| propertiesDefinition[bytes32("lockupExpiration")].code = bytes32("lockupExpiration"); |
| propertiesDefinition[bytes32("lockupExpiration")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("lockupExpiration")].from = 144; |
| propertiesDefinition[bytes32("lockupExpiration")].len = 40; |
| |
| propertiesDefinition[bytes32("ats")].code = bytes32("ats"); |
| propertiesDefinition[bytes32("ats")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("ats")].from = 184; |
| propertiesDefinition[bytes32("ats")].len = 1; |
| |
| propertiesDefinition[bytes32("blocked")].code = bytes32("blocked"); |
| propertiesDefinition[bytes32("blocked")].bucket = bytes32("general"); |
| propertiesDefinition[bytes32("blocked")].from = 185; |
| propertiesDefinition[bytes32("blocked")].len = 1; |
| |
| propertiesDefinition[bytes32("investorId")].code = bytes32("investorId"); |
| propertiesDefinition[bytes32("investorId")].bucket = bytes32("investorId"); |
| propertiesDefinition[bytes32("investorId")].from = 0; |
| �� propertiesDefinition[bytes32("investorId")].len = 256; |
| |
| propertiesDefinition[bytes32("kycReference")].code = bytes32("kycReference"); |
| propertiesDefinition[bytes32("kycReference")].bucket = bytes32("kycReference"); |
| propertiesDefinition[bytes32("kycReference")].from = 0; |
| propertiesDefinition[bytes32("kycReference")].len = 256; |
| |
| propertiesDefinition[bytes32("amlReference")].code = bytes32("amlReference"); |
| propertiesDefinition[bytes32("amlReference")].bucket = bytes32("amlReference"); |
| propertiesDefinition[bytes32("amlReference")].from = 0; |
| propertiesDefinition[bytes32("amlReference")].len = 256; |
| |
| propertiesDefinition[bytes32("accreditedReference")].code = bytes32("accreditedReference"); |
| propertiesDefinition[bytes32("accreditedReference")].bucket = bytes32("accreditedReference"); |
| propertiesDefinition[bytes32("accreditedReference")].from = 0; |
| propertiesDefinition[bytes32("accreditedReference")].len = 256; |
| } |
| } |
| |
| contract StandardWhitelistFactory is Factory { |
| function createInstance(address[] memory validators, bytes32[] memory codes, bytes32[] memory buckets, uint8[] memory froms, uint16[] memory lens) |
| public returns(address) |
| { |
| StandardWhitelist instance = new StandardWhitelist(validators, codes, buckets, froms, lens); |
| instance.transferOwnership(msg.sender); |
| addInstance(address(instance)); |
| return address(instance); |
| } |
| } |