diff --git a/contracts/storage/StorageInterface.sol b/contracts/storage/StorageInterface.sol index c65d1bf..bf67e4f 100644 --- a/contracts/storage/StorageInterface.sol +++ b/contracts/storage/StorageInterface.sol @@ -1,22 +1,22 @@ -pragma solidity ^0.4.18; +pragma solidity ^0.4.21; interface StorageInterface { - function transferOwnership (address newOwner) public; // Owners only: revoke access from the calling account and grant access to newOwner - function grantAccess (address newOwner) public; // Owners only: just grant access to newOwner without revoking the access from the current owner - function revokeAccess (address previousOwner) public; // Just revoke access from the current owner - function isOwner (address addr) public view returns(bool); - function getUint (bytes32 record) public view returns (uint); - function getString (bytes32 record) public view returns (string); - function getAddress (bytes32 record) public view returns (address); - function getBytes (bytes32 record) public view returns (bytes); - function getBoolean (bytes32 record) public view returns (bool); - function getInt (bytes32 record) public view returns (int); - function setString (bytes32 record, string value) public; - function setUint (bytes32 record, uint value) public; - function setAddress (bytes32 record, address value) public; - function setBytes (bytes32 record, bytes value) public; - function setBoolean (bytes32 record, bool value) public; - function setInt (bytes32 record, int value) public; + function transferOwnership (address newOwner) external; // Owners only: revoke access from the calling account and grant access to newOwner + function grantAccess (address newOwner) external; // Owners only: just grant access to newOwner without revoking the access from the current owner + function revokeAccess (address previousOwner) external; // Just revoke access from the current owner + function isOwner (address addr) external view returns(bool); + function getUint (bytes32 record) external view returns (uint); + function getString (bytes32 record) external view returns (string); + function getAddress (bytes32 record) external view returns (address); + function getBytes (bytes32 record) external view returns (bytes); + function getBoolean (bytes32 record) external view returns (bool); + function getInt (bytes32 record) external view returns (int); + function setString (bytes32 record, string value) external; + function setUint (bytes32 record, uint value) external; + function setAddress (bytes32 record, address value) external; + function setBytes (bytes32 record, bytes value) external; + function setBoolean (bytes32 record, bool value) external; + function setInt (bytes32 record, int value) external; } \ No newline at end of file diff --git a/contracts/teams/TeamContracts.sol b/contracts/teams/TeamContracts.sol index b19c940..043b1d1 100644 --- a/contracts/teams/TeamContracts.sol +++ b/contracts/teams/TeamContracts.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.18; +pragma solidity ^0.4.21; import "../storage/TeamsStorageController.sol"; import "../storage/StorageInterface.sol"; @@ -13,6 +13,7 @@ contract TeamContracts is TeamsStorageController { event Payout(uint indexed contractId, uint amount, address triggeredBy); event ContractCompleted(uint indexed contractId, bool extended); // Boolean extended: whether the contract was extended to a new period event ContractProlongationFailed(uint indexed contractId); + event Upgraded(address newContract); address public erc20TokenAddress; // Address of authorized token address public dreamTeamAddress; // Authorized account for managing teams @@ -30,7 +31,7 @@ contract TeamContracts is TeamsStorageController { function createTeam (address teamOwnerAccount) dreamTeamOnly public returns(uint) { uint teamId = storageAddTeam(teamOwnerAccount); - TeamCreated(teamId); + emit TeamCreated(teamId); return teamId; } @@ -43,7 +44,7 @@ contract TeamContracts is TeamsStorageController { function addMember (uint teamId, address memberAccount, uint agreementMinutes, uint agreementValue, bool singleTermAgreement, uint contractId) dreamTeamOnly public { storageDecTeamBalance(teamId, agreementValue); // throws if balance goes negative storageAddTeamMember(teamId, memberAccount, agreementMinutes, agreementValue, singleTermAgreement, contractId); - TeamMemberAdded(contractId); + emit TeamMemberAdded(contractId); } function removeMember (uint teamId, uint contractId) dreamTeamOnly public { @@ -55,7 +56,7 @@ contract TeamContracts is TeamsStorageController { if (payoutDate <= now) { // return full amount to the player ERC20TokenInterface(erc20TokenAddress).transfer(storageGetTeamMemberAddress(teamId, uint(memberIndex)), agreementValue); - TeamMemberRemoved(contractId, agreementValue, 0); + emit TeamMemberRemoved(contractId, agreementValue, 0); } else { // if (payoutDate > now): return a part of the amount based on the number of days spent in the team, in proportion uint agreementMinutes = storageGetTeamMemberAgreementMinutes(teamId, uint(memberIndex)); uint agreementValue = storageGetTeamMemberAgreementValue(teamId, uint(memberIndex)); @@ -65,7 +66,7 @@ contract TeamContracts is TeamsStorageController { ERC20TokenInterface(erc20TokenAddress).transfer(storageGetTeamMemberAddress(teamId, uint(memberIndex)), amountToPayout); if (amountToPayout < agreementValue) storageIncTeamBalance(teamId, agreementValue - amountToPayout); // unlock the rest of the funds - TeamMemberRemoved(contractId, amountToPayout, agreementValue - amountToPayout); + emit TeamMemberRemoved(contractId, amountToPayout, agreementValue - amountToPayout); } // Actually delete team member from a storage @@ -92,23 +93,23 @@ contract TeamContracts is TeamsStorageController { value = storageGetTeamMemberAgreementValue(teamId, index); contractId = storageGetMemberContractId(teamId, index); ERC20TokenInterface(erc20TokenAddress).transfer(storageGetTeamMemberAddress(teamId, index), value); - Payout(contractId, value, msg.sender); + emit Payout(contractId, value, msg.sender); if (storageGetTeamMemberSingleTermAgreement(teamId, index)) { // Terminate the contract due to a single-term agreement storageDeleteTeamMember(teamId, index); - ContractCompleted(contractId, false); + emit ContractCompleted(contractId, false); } else { // Extend the contract if (storageGetTeamBalance(teamId) < value) { // No funds in the team: auto extend is not possible, remove the team member storageDeleteTeamMember(teamId, index); - ContractCompleted(contractId, false); - ContractProlongationFailed(contractId); + emit ContractCompleted(contractId, false); + emit ContractProlongationFailed(contractId); } else { storageDecTeamBalance(teamId, value); storageSetTeamMemberPayoutDate( - teamId, - index, + teamId, + index, storageGetTeamMemberPayoutDate(teamId, index) + storageGetTeamMemberAgreementMinutes(teamId, index) * 60 ); - ContractCompleted(contractId, true); + emit ContractCompleted(contractId, true); } } } @@ -131,7 +132,7 @@ contract TeamContracts is TeamsStorageController { ERC20TokenInterface(erc20TokenAddress).transferFrom(msg.sender, address(this), amount) ); storageIncTeamBalance(teamId, amount); - TeamBalanceRefilled(teamId, msg.sender, amount); + emit TeamBalanceRefilled(teamId, msg.sender, amount); } /** @@ -139,11 +140,16 @@ contract TeamContracts is TeamsStorageController { * @param newDeployedTeamContracts - Deployed teams contract. */ function upgrade (address newDeployedTeamContracts) dreamTeamOnly public { - require(TeamContracts(newDeployedTeamContracts).db() == db); // Check whether the switch is performed between contracts linked to the same database - require(TeamContracts(newDeployedTeamContracts).erc20TokenAddress() == erc20TokenAddress); // Check whether they share the same token as well - // However, the owner of the contract can be different in the new contract, no restrictions apply here + require(TeamContracts(newDeployedTeamContracts).db() == db); // Switch between contracts linked to the same storage + // Do not enforce the same token contract for new TeamContracts; this took place when a token is upgraded (changed) + // In case of the new token contract, a special care should be taken into account to preserve the same balance in + // tokens in the newly deployed token contract. + // - require(TeamContracts(newDeployedTeamContracts).erc20TokenAddress() == erc20TokenAddress); StorageInterface(db).transferOwnership(newDeployedTeamContracts); // Revoke access from the current contract and grant access to a new one - ERC20TokenInterface(erc20TokenAddress).transfer(newDeployedTeamContracts, ERC20TokenInterface(erc20TokenAddress).balanceOf(this)); // Move all funds to a new contract + ERC20TokenInterface(erc20TokenAddress).transfer( // Move all funds to a new contract + newDeployedTeamContracts, ERC20TokenInterface(erc20TokenAddress).balanceOf(this) + ); + emit Upgraded(newDeployedTeamContracts); selfdestruct(newDeployedTeamContracts); } diff --git a/contracts/token/DTT.sol b/contracts/token/DTT.sol new file mode 100644 index 0000000..0bc4319 --- /dev/null +++ b/contracts/token/DTT.sol @@ -0,0 +1,338 @@ +pragma solidity ^0.4.21; + +interface tokenRecipient { + function receiveApproval (address from, uint256 value, address token, bytes extraData) external; +} + +/** + * DreamTeam token contract. It implements the next capabilities: + * 1. Standard ERC20 functionality. [OK] + * 2. Additional utility function approveAndCall. [OK] + * 3. Function to rescue "lost forever" tokens, which were accidentally sent to the contract address. [OK] + * 4. Additional transfer and approve functions which allow to distinct the transaction signer and executor, + * which enables accounts with no Ether on their balances to make token transfers and use DreamTeam services. [TEST] + * 5. Token sale distribution rules. [OK] + */ +contract DTT { + + string public name; + string public symbol; + uint8 public decimals = 6; // Makes JavaScript able to handle precise calculations (until totalSupply < 9 milliards) + uint256 public totalSupply; + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + mapping(address => mapping(uint => bool)) public usedSigIds; // Used in *ViaSignature(..) + address public tokenDistributor; // Account authorized to distribute tokens only during the token distribution event + address public rescueAccount; // Account authorized to withdraw tokens accidentally sent to this contract + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + bytes public ethSignedMessagePrefix = "\x19Ethereum Signed Message:\n32"; + enum sigStandard { typed, personal, stringHex } + enum sigDestination { transfer, approve, approveAndCall } + bytes32 public sigDestinationTransfer = keccak256( + "address Token Contract Address", + "address Sender's Address", + "address Recipient's Address", + "uint256 Amount to Transfer (last six digits are decimals)", + "uint256 Fee in Tokens Paid to Executor (last six digits are decimals)", + "uint256 Signature Expiration Timestamp (unix timestamp)", + "uint256 Signature ID", + "uint8 Signature Standard" + ); // `transferViaSignature`: keccak256(address(this), from, to, value, fee, deadline, sigId, sigStandard) + bytes32 public sigDestinationApprove = keccak256( + "address Token Contract Address", + "address Withdraw Approval Address", + "address Withdraw Recipient Address", + "uint256 Amount to Transfer (last six digits are decimals)", + "uint256 Fee in Tokens Paid to Executor (last six digits are decimals)", + "uint256 Signature Expiration Timestamp (unix timestamp)", + "uint256 Signature ID", + "uint8 Signature Standard" + ); // `approveViaSignature`: keccak256(address(this), from, spender, value, fee, deadline, sigId, sigStandard) + bytes32 public sigDestinationApproveAndCall = keccak256( // `approveAndCallViaSignature` + "address Token Contract Address", + "address Withdraw Approval Address", + "address Withdraw Recipient Address", + "uint256 Amount to Transfer (last six digits are decimals)", + "bytes Data to Transfer", + "uint256 Fee in Tokens Paid to Executor (last six digits are decimals)", + "uint256 Signature Expiration Timestamp (unix timestamp)", + "uint256 Signature ID", + "uint8 Signature Standard" + ); // `approveAndCallViaSignature`: keccak256(address(this), from, spender, value, extraData, fee, deadline, sigId, sigStandard) + + function DTT (string tokenName, string tokenSymbol) public { // todo: remove initial supply + name = tokenName; + symbol = tokenSymbol; + rescueAccount = tokenDistributor = msg.sender; + } + + /** + * Utility internal function used to safely transfer `value` tokens `from` -> `to`. Throws if transfer is impossible. + */ + function internalTransfer (address from, address to, uint value) internal { + // Prevent people from accidentally burning their tokens + uint256 wrap prevention + require(to != 0x0 && balanceOf[from] >= value && balanceOf[to] + value >= balanceOf[to]); + balanceOf[from] -= value; + balanceOf[to] += value; + emit Transfer(from, to, value); + } + + /** + * Utility internal function used to safely transfer `value1` tokens `from` -> `to1`, and `value2` tokens + * `from` -> `to2`, minimizing gas usage (calling `internalTransfer` twice is more expensive). Throws if + * transfers are impossible. + */ + function internalDoubleTransfer (address from, address to1, uint value1, address to2, uint value2) internal { + require( // Prevent people from accidentally burning their tokens + uint256 wrap prevention + to1 != 0x0 && to2 != 0x0 && balanceOf[from] >= + value1 + value2 && balanceOf[to1] + value1 >= balanceOf[to1] && balanceOf[to2] + value2 >= balanceOf[to2] + ); + balanceOf[from] -= value1 + value2; + balanceOf[to1] += value1; + emit Transfer(from, to1, value1); + if (value2 > 0) { + balanceOf[to2] += value2; + emit Transfer(from, to2, value2); + } + } + + /** + * Transfer `value` tokens to `to` address from the account of sender. + * @param to - the address of the recipient + * @param value - the amount to send + */ + function transfer (address to, uint256 value) public returns (bool) { + internalTransfer(msg.sender, to, value); + return true; + } + + /** + * Internal method that makes sure that signature corresponds to a given data and some other constraints are met. + */ + function requireSignature ( + bytes32 data, address from, uint256 deadline, uint256 sigId, bytes sig, sigStandard std, sigDestination signDest + ) internal { + bytes32 r; + bytes32 s; + uint8 v; + assembly { // solium-disable-line security/no-inline-assembly + r := mload(add(sig, 32)) + s := mload(add(sig, 64)) + v := byte(0, mload(add(sig, 96))) + } + if (v < 27) + v += 27; + require(block.timestamp <= deadline && !usedSigIds[from][sigId]); // solium-disable-line security/no-block-members + if (std == sigStandard.typed) { // Typed signature + require( + from == ecrecover( + keccak256( + signDest == sigDestination.transfer + ? sigDestinationTransfer + : signDest == sigDestination.approve + ? sigDestinationApprove + : sigDestinationApproveAndCall, + data + ), + v, r, s + ) + ); + } else if (std == sigStandard.personal) { // Ethereum signed message signature + require(from == ecrecover(keccak256(ethSignedMessagePrefix, data), v, r, s)); + } else { // == 2; Signed string hash signature (the most expensive but universal) + require(from == ecrecover(keccak256(ethSignedMessagePrefix, hexToString(data)), v, r, s)); + } + usedSigIds[from][sigId] = true; + } + + function hexToString (bytes32 sig) internal pure returns (string) { // TODO: convert to two uint256 and test gas + bytes memory str = new bytes(64); + for (uint8 i = 0; i < 32; ++i) { + str[2 * i] = byte((uint8(sig[i]) / 16 < 10 ? 48 : 87) + uint8(sig[i]) / 16); + str[2 * i + 1] = byte((uint8(sig[i]) % 16 < 10 ? 48 : 87) + (uint8(sig[i]) % 16)); + } + return string(str); + } + + /** + * This function distincts transaction signer from transaction executor. It allows anyone to transfer tokens + * from the `from` account by providing a valid signature, which can only be obtained from the `from` account + * owner. + * Note that passed parameters must be unique and cannot be passed twice (prevents replay attacks). When there's + * a need to get a signature for the same transaction again, adjust the `deadline` parameter accordingly. + */ + function transferViaSignature ( + address from, // Account to transfer tokens from, which signed all below parameters + address to, // Account to transfer tokens to + uint256 value, // Value to transfer + uint256 fee, // Fee paid to transaction executor + uint256 deadline, // Time until the transaction can be executed by the delegate + uint256 sigId, // A "nonce" for the transaction. The same sigId cannot be used twice + bytes sig, // Signature made by `from`, which is the proof of `from`'s agreement with the above parameters + sigStandard sigStd // Determines how signature was made, because some standards are not implemented in some wallets (yet) + ) public returns (bool) { + requireSignature( + keccak256(address(this), from, to, value, fee, deadline, sigId), + from, deadline, sigId, sig, sigStd, sigDestination.transfer + ); + internalDoubleTransfer(from, to, value, msg.sender, fee); + return true; + } + + /** + * Transfer `value` tokens to `to` address from the `from` account, using the previously set allowance. + * @param from - the address to transfer tokens from + * @param to - the address of the recipient + * @param value - the amount to send + */ + function transferFrom (address from, address to, uint256 value) public returns (bool) { + require(value <= allowance[from][msg.sender]); // Test whether allowance was set + allowance[from][msg.sender] -= value; + internalTransfer(from, to, value); + return true; + } + + /** + * Allow `spender` to take `value` tokens from the transaction sender's account. + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param spender - the address authorized to spend + * @param value - the maximum amount they can spend + */ + function approve (address spender, uint256 value) public returns (bool) { + allowance[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + /** + * Same as `transferViaSignature`, but for approval. + */ + function approveViaSignature ( + address from, // Account to approve expenses on, which signed all below parameters + address spender, // Account to allow to do expenses + uint256 value, // Value to approve + uint256 fee, // Fee paid to transaction executor + uint256 deadline, // Time until the transaction can be executed by the executor + uint256 sigId, // A "nonce" for the transaction. The same sigId cannot be used twice + bytes sig, // Signature made by `from`, which is the proof of `from`'s agreement with the above parameters + sigStandard sigStd + ) public returns (bool) { + requireSignature( + keccak256(address(this), from, spender, value, fee, deadline, sigId), + from, deadline, sigId, sig, sigStd, sigDestination.approve + ); + allowance[from][spender] = value; + emit Approval(from, spender, value); + internalTransfer(from, msg.sender, value); + return true; + } + + /** + * Utility function, which acts the same as approve(...) does, but also calls `receiveApproval` function on a + * `spender` address, which is usually the address of the smart contract. In the same call, smart contract can + * withdraw tokens from the sender's account and receive additional `extraData` for processing. + * @param spender - the address to be authorized to spend tokens + * @param value - the max amount the `spender` can withdraw + * @param extraData - some extra information to send to the approved contract + */ + function approveAndCall (address spender, uint256 value, bytes extraData) public returns (bool) { + approve(spender, value); + tokenRecipient(spender).receiveApproval(msg.sender, value, this, extraData); + return true; + } + + /** + * Same as `approveViaSignature`, but for approveAndCall. + */ + function approveAndCallViaSignature ( + address from, // Account to approve expenses on, which signed all below parameters + address spender, // Account to allow to do expenses + uint256 value, // Value to transfer + bytes extraData, // Additional data to pass to a `tokenRecipient` + uint256 fee, // Fee paid to transaction executor + uint256 deadline, // Time until the transaction can be executed by the delegate + uint256 sigId, // A "nonce" for the transaction. The same sigId cannot be used twice + bytes sig, // Signature made by `from`, which is the proof of `from`'s agreement with the above parameters + sigStandard sigStd + ) public returns (bool) { + requireSignature( + keccak256(address(this), from, spender, value, extraData, fee, deadline, sigId), + from, deadline, sigId, sig, sigStd, sigDestination.approveAndCall + ); + allowance[from][spender] = value; + emit Approval(from, spender, value); + tokenRecipient(spender).receiveApproval(from, value, this, extraData); + internalTransfer(from, msg.sender, value); + return true; + } + + /** + * `tokenDistributor` is authorized to distribute tokens to the parties who participated in the token sale by the + * time the `lastMint` function is triggered, which closes the ability to mint any new tokens forever. + * @param recipients - Addresses of token recipients + * @param amounts - Corresponding amount of each token recipient in `recipients` + */ + function multiMint (address[] recipients, uint256[] amounts) external { + + // Once the token distribution ends, tokenDistributor will become 0x0 and multiMint will never work + require(tokenDistributor != 0x0 && tokenDistributor == msg.sender && recipients.length == amounts.length); + + uint total = 0; + + for (uint i = 0; i < recipients.length; ++i) { + balanceOf[recipients[i]] += amounts[i]; + total += amounts[i]; + emit Transfer(0x0, recipients[i], amounts[i]); + } + + totalSupply += total; + + } + + /** + * The last mint that will ever happen. Disables the multiMint function and mints remaining 40% of tokens (in + * regard of 60% tokens minted before) to a `tokenDistributor` address. + */ + function lastMint () external { + + require(tokenDistributor != 0x0 && tokenDistributor == msg.sender && totalSupply > 0); + + uint256 remaining = totalSupply * 40 / 60; // Portion of tokens for DreamTeam (40%) + + // To make the total supply rounded (no fractional part), subtract the fractional part from DreamTeam's balance + uint256 fractionalPart = (remaining + totalSupply) % (uint256(10) ** decimals); + if (fractionalPart <= remaining) + remaining -= fractionalPart; // Remove the fractional part to round the totalSupply + + balanceOf[tokenDistributor] += remaining; + emit Transfer(0x0, tokenDistributor, remaining); + + totalSupply += remaining; + tokenDistributor = 0x0; // Disable multiMint and lastMint functions forever + + } + + /** + * ERC20 token is not designed to hold any tokens itself. This fallback function allows to rescue tokens + * accidentally sent to the address of this smart contract. + */ + function rescueTokens (DTT tokenContract, uint256 tokens) public { + require(msg.sender == rescueAccount); + tokenContract.approve(rescueAccount, tokens); + } + + /** + * Utility function that allows to change the rescueAccount address. + */ + function changeRescueAccount (address newRescueAccount) public { + require(msg.sender == rescueAccount); + rescueAccount = newRescueAccount; + } + +} diff --git a/contracts/token/ERC20TokenInterface.sol b/contracts/token/ERC20TokenInterface.sol index 956ecf1..fe17379 100644 --- a/contracts/token/ERC20TokenInterface.sol +++ b/contracts/token/ERC20TokenInterface.sol @@ -1,10 +1,10 @@ -pragma solidity ^0.4.18; +pragma solidity ^0.4.21; contract ERC20TokenInterface { - function totalSupply () public constant returns (uint); - function balanceOf (address tokenOwner) public constant returns (uint balance); - function transfer (address to, uint tokens) public returns (bool success); - function transferFrom (address from, address to, uint tokens) public returns (bool success); + function totalSupply () external constant returns (uint); + function balanceOf (address tokenOwner) external constant returns (uint balance); + function transfer (address to, uint tokens) external returns (bool success); + function transferFrom (address from, address to, uint tokens) external returns (bool success); } \ No newline at end of file diff --git a/readme.md b/readme.md index 0653bd5..325ae85 100644 --- a/readme.md +++ b/readme.md @@ -2,34 +2,33 @@ This repository contains the code of the smart contracts used within [DreamTeam](https://dreamteam.gg) services. This repository is provided for informational purposes only. -Currently all smart contracts in this repository [are used](https://ropsten.etherscan.io/token/0x671c81d8731f9582f17e7519f46243040e7d9642) for test purposes in Ethereum test network (Ropsten). Smart contracts in the live network (mainnet) can be slightly different and some of them may be replaced. +Currently all smart contracts in this repository [are used](https://ropsten.etherscan.io/token/0x671c81d8731f9582f17e7519f46243040e7d9642) for test purposes in Ethereum test network (Ropsten). Smart contracts in the live network (mainnet) can be slightly different due to active development process. + +## Smart Contracts Source Code (entry points) + ++ [DreamTeam Test Token (TDTT)](contracts/token/TDTT.sol) (test token currently used in Ethereum test network) ++ [DreamTeam Token (DTT)](contracts/token/DTT.sol) (potential DreamTeam token contract) ++ [Team Contracts Manager Contract](contracts/teams/TeamContracts.sol) (smart contract for team compensation payments) ## Description -This repository contains smart contracts code which manage contracts (agreements) between teams (team owners) -and players (team members). Some of these smart contracts are meant to be upgradable, preserving opportunity for DreamTeam to add more and more functionality in the future. +This repository contains smart contracts used withing the DreamTeam platform. Some of these smart contracts are +upgradable by design, preserving opportunity for DreamTeam to add more and more functionality in the future. The whole dApp (smart contracts) keeps track only of those parts of DreamTeam which are somehow related to crypto assets. This includes team creation (team owner assignment), adding or removing team members and paying to them, token transfers and so on. We have an authorized address (DreamTeam address) to manage teams (create new teams, add/remove -team members with appropriate rules). The crucial thing is that all payouts to team members are guaranteed -once an agreement is established. Once a team contract (meaning the contract between the team owner and a +team members with appropriate rules). The crucial thing is that all payouts to team members **are guaranteed +once an agreement is established**. Once a team contract (meaning the contract between the team owner and a player) is established (technically speaking, when DreamTeam account will actually trigger an `addMember` -function of a smart contract), the player is **guaranteed** to receive their tokens due to a publicly +function of a smart contract), the player is guaranteed to receive their tokens due to a publicly available function `payout` in TeamContracts smart contract. Normally, in the future, DreamTeam is going to trigger payouts once a day, massively, for all teams which need to be paid out, for a little fee in tokens. If DreamTeam for some unknown reason does not trigger payouts, team members theirselves can trigger ones, by using any services publicly available like -Etherscan, MyEtherWallet or so. - -## Smart Contracts - -Currently, we expose two smart contracts for a public use: - -+ [contracts/teams/TeamContracts.sol](contracts/teams/TeamContracts.sol) (an upgradeable/replaceable smart contract) -+ [contracts/token/TDTT.sol](contracts/token/TDTT.sol) (ERC20 test token: a simple token deployed for testing purposes only) +[Etherscan](https://ropsten.etherscan.io), MyEtherWallet or others to trigger payout. ## Smart Contract Addresses (Test Network)