SingularDTV: A Decentralized “Netflix” on Ethereum has announced a bug bounty.
From Weekend Bug Bounty – 5 BTC for Major Bugs! 10 BTC for Fatal Flaws! self.ethereum
Weekend Bug Bounty – 5 BTC for Major Bugs! 10 BTC for Fatal Flaws!
We’ve had several participants and a few false alarms, but so far no one has found a bug in our Smart Contract System yet. We’re challenging anyone out there, over the weekend, to find flaws in our system. Anonymous submissions welcome!
Open for submissions NOW! Deadline for submissions is Tuesday, 12:01 am, September 6th.
Contact Email your submissions to: bounty@singulardtv.com
Don’t forget to include your BTC or ETH address so you can be rewarded. (If more than 1 address is specified, only one will be used at the discretion of the bounty program administrators.)
Major bugs will be rewarded 5 BTC or ETH equivalent. Much higher rewards are possible (up to 10 BTC or ETH equivalent) in case of very severe vulnerabilities.
For questions use the forum https://forum.singulardtv.com/t/singulardtv-bug-bounty/47 or email bounty@singulardtv.com.
DETAILS
The SingularDTV Token Launch spec is at: https://singulardtv.com/resources/default/pdf/SingularDTV-TokenLaunchSpecs.pdf
Most of the rules on https://bounty.ethereum.org apply. For example: First come, first serve. Issues that have already been submitted by another user or are already known, such as these, to the team are not eligible for bounty rewards. Scope of SingularDTV Bounty Program
In scope: https://github.com/ConsenSys/singulardtv-contracts
• SingularDTVCrowdfunding.sol
• SingularDTVFund.sol
• SingularDTVToken.sol
• StandardToken.sol
https://github.com/ConsenSys/singulardtv-contracts
• https://github.com/ConsenSys/eth-lightwallet/tree/2a61eab456bb2c0e97c41be209887c8ca9ad43bb/lib
• https://gist.github.com/miladmostavi/508f1628e543d10b314d901b8fd9097d
Out of scope: • MistWallet.sol
• Bugs related to Internet Explorer • All browser rendering bugs that don’t affect the display of critical information such as ETH, SNGLS • Most user experience improvements on the frontend
Again, the spec is at: https://singulardtv.com/resources/default/pdf/SingularDTV-TokenLaunchSpecs.pdf
Examples of what’s in scope: • Being able to withdraw more ETH than contributed • Being able to obtain more tokens (SNGLS) than expected • Being able to obtain SNGLS from someone without their permission • Demonstrating that the workshop can transfer their SNGLS before 2 years • Being able to put SingularDTVCrowdfunding in emergency state by making the checkInvariants() throw • Bugs in eth-lightwallet that lead to loss or theft of ETH • Bugs causing a transaction to be sent that was different from what user confirmed: for example, user transfers 10 SNGLS in the UI, but exactly 10 wasn’t transferred.
Examples of what’s out of scope: • Being able to softWithdraw another person’s revenue • Most user experience improvements on the frontend, for example some part of the website doesn’t update unless the page is refreshed
Thank you! bounty@singulardtv.com
Source Code
Source code from github.com/ConsenSys/singulardtv-contracts/contracts @ 02:38 Sep 3 2016 UTC.
AbstractCampaign.sol
contract Campaign {
/// @notice the campaign name
/// @return contractual metadata which specifies the campaign name as a string
function name() constant returns(string) {}
/// @notice use to determine the contribution method abi
/// @return will return a string that is the exact contributeMethodABI
function contributeMethodABI() constant returns(string) {}
/// @notice use to determine the contribution method abi
/// @return will return a string that is the exact contributeMethodABI
function refundMethodABI() constant returns(string) {}
/// @notice use to determine the contribution method abi
/// @return will return a string that is the exact contributeMethodABI
function payoutMethodABI() constant returns(string) {}
/// @notice use to determine the beneficiary destination for the campaign
/// @return the beneficiary address that will receive the campaign payout
function beneficiary() constant returns(address) {}
/// @notice the time at which the campaign fails or succeeds
/// @return the uint unix timestamp at which time the campaign expires
function expiry() constant returns(uint256 timestamp) {}
/// @notice the goal the campaign must reach in order for it to succeed
/// @return the campaign funding goal specified in wei as a uint256
function fundingGoal() constant returns(uint256 amount) {}
/// @notice the goal the campaign must reach in order for it to succeed
/// @return the campaign funding goal specified in wei as a uint256
function amountRaised() constant returns(uint256 amount) {}
// Campaign events
event ContributionMade (address _contributor);
event RefundPayoutClaimed(uint256 _contributionID);
event BeneficiaryPayoutClaimed (address _beneficiary, uint256 _payoutAmount);
}
AbstractSingularDTVCrowdfunding.sol
contract SingularDTVCrowdfunding {
function oneYearPassed() returns (bool);
function startDate() returns (uint);
function CROWDFUNDING_PERIOD() returns (uint);
function TOKEN_TARGET() returns (uint);
function valuePerShare() returns (uint);
function fundBalance() returns (uint);
function campaignEndedSuccessfully() returns (bool);
}
AbstractToken.sol
/// Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20
/// @title Abstract token contract - Functions to be implemented by token contracts.
/// @author Stefan George - <stefan.george@consensys.net>
contract Token {
// This is not an abstract function, because solc won't recognize generated getter functions for public variables as functions
function totalSupply() constant returns (uint256 supply) {}
function balanceOf(address owner) constant returns (uint256 balance);
function transfer(address to, uint256 value) returns (bool success);
function transferFrom(address from, address to, uint256 value) returns (bool success);
function approve(address spender, uint256 value) returns (bool success);
function allowance(address owner, address spender) constant returns (uint256 remaining);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
AbstractSingularDTVToken.sol
import "AbstractToken.sol";
contract SingularDTVToken is Token {
function issueTokens(address _for, uint tokenCount) returns (bool);
}
AbstractSingularDTVFund.sol
contract SingularDTVFund {
function workshop() returns (address);
function softWithdrawRevenueFor(address forAddress) returns (uint);
}
SingularDTVCrowdfunding.sol
import "AbstractSingularDTVToken.sol";
import "AbstractSingularDTVFund.sol";
/// @title Crowdfunding contract - Implements crowdfunding functionality.
/// @author Stefan George - <stefan.george@consensys.net>
contract SingularDTVCrowdfunding {
/*
* External contracts
*/
SingularDTVToken public singularDTVToken;
SingularDTVFund public singularDTVFund;
/*
* Constants
*/
uint constant public CAP = 1000000000; // 1B tokens is the maximum amount of tokens
uint constant public CROWDFUNDING_PERIOD = 4 weeks; // 1 month
uint constant public TOKEN_LOCKING_PERIOD = 1 years; // 1 years
uint constant public TOKEN_TARGET = 534000000; // 34M Tokens more than the initial 500M, around 42,500 ETH
/*
* Enums
*/
enum Stages {
CrowdfundingGoingAndGoalNotReached,
CrowdfundingEndedAndGoalNotReached,
CrowdfundingGoingAndGoalReached,
CrowdfundingEndedAndGoalReached
}
/*
* Storage
*/
address public owner;
uint public startDate;
uint public fundBalance;
uint public baseValue = 1250 szabo; // 0.00125 ETH
uint public valuePerShare = baseValue; // 0.00125 ETH
// investor address => investment in Wei
mapping (address => uint) public investments;
// Initialize stage
Stages public stage = Stages.CrowdfundingGoingAndGoalNotReached;
/*
* Modifiers
*/
modifier noEther() {
if (msg.value > 0) {
throw;
}
_
}
modifier onlyOwner() {
// Only owner is allowed to do this action.
if (msg.sender != owner) {
throw;
}
_
}
modifier minInvestment() {
// User has to invest at least the ether value of one share.
if (msg.value < valuePerShare) {
throw;
}
_
}
modifier atStage(Stages _stage) {
if (stage != _stage) {
throw;
}
_
}
modifier atStageOR(Stages _stage1, Stages _stage2) {
if (stage != _stage1 && stage != _stage2) {
throw;
}
_
}
modifier timedTransitions() {
uint crowdfundDuration = now - startDate;
if (crowdfundDuration >= 22 days) {
valuePerShare = baseValue * 1500 / 1000;
}
else if (crowdfundDuration >= 18 days) {
valuePerShare = baseValue * 1375 / 1000;
}
else if (crowdfundDuration >= 14 days) {
valuePerShare = baseValue * 1250 / 1000;
}
else if (crowdfundDuration >= 10 days) {
valuePerShare = baseValue * 1125 / 1000;
}
else {
valuePerShare = baseValue;
}
if (crowdfundDuration >= CROWDFUNDING_PERIOD) {
if (stage == Stages.CrowdfundingGoingAndGoalNotReached) {
stage = Stages.CrowdfundingEndedAndGoalNotReached;
}
else if (stage == Stages.CrowdfundingGoingAndGoalReached) {
stage = Stages.CrowdfundingEndedAndGoalReached;
}
}
_
}
/*
* Contract functions
*/
/// dev Validates invariants.
function checkInvariants() constant internal {
if (fundBalance > this.balance) {
throw;
}
}
/// @dev Can be triggered if an invariant fails.
function emergencyCall()
external
noEther
returns (bool)
{
if (fundBalance > this.balance) {
if (this.balance > 0 && !singularDTVFund.workshop().send(this.balance)) {
throw;
}
return true;
}
return false;
}
/// @dev Allows user to fund the campaign if campaign is still going and cap not reached. Returns share count.
function fund()
external
timedTransitions
atStageOR(Stages.CrowdfundingGoingAndGoalNotReached, Stages.CrowdfundingGoingAndGoalReached)
minInvestment
returns (uint)
{
uint tokenCount = msg.value / valuePerShare; // Token count is rounded down. Investment should be multiples of valuePerShare.
if (singularDTVToken.totalSupply() + tokenCount > CAP) {
// User wants to buy more shares than available. Set shares to possible maximum.
tokenCount = CAP - singularDTVToken.totalSupply();
}
uint investment = tokenCount * valuePerShare; // Ether invested by backer.
// Send change back to user.
if (msg.value > investment && !msg.sender.send(msg.value - investment)) {
throw;
}
// Update fund's and user's balance and total supply of shares.
fundBalance += investment;
investments[msg.sender] += investment;
if (!singularDTVToken.issueTokens(msg.sender, tokenCount)) {
// Tokens could not be issued.
throw;
}
// Update stage
if (stage == Stages.CrowdfundingGoingAndGoalNotReached) {
if (singularDTVToken.totalSupply() >= TOKEN_TARGET) {
stage = Stages.CrowdfundingGoingAndGoalReached;
}
}
// not an else clause for the edge case that the CAP and TOKEN_TARGET are reached with one big funding
if (stage == Stages.CrowdfundingGoingAndGoalReached) {
if (singularDTVToken.totalSupply() == CAP) {
stage = Stages.CrowdfundingEndedAndGoalReached;
}
}
checkInvariants();
return tokenCount;
}
/// @dev Allows user to withdraw his funding if crowdfunding ended and target was not reached. Returns success.
function withdrawFunding()
external
noEther
timedTransitions
atStage(Stages.CrowdfundingEndedAndGoalNotReached)
returns (bool)
{
// Update fund's and user's balance and total supply of shares.
uint investment = investments[msg.sender];
investments[msg.sender] = 0;
fundBalance -= investment;
// Send funds back to user.
if (investment > 0 && !msg.sender.send(investment)) {
throw;
}
checkInvariants();
return true;
}
/// @dev Withdraws funding for workshop. Returns success.
function withdrawForWorkshop()
external
noEther
timedTransitions
atStage(Stages.CrowdfundingEndedAndGoalReached)
returns (bool)
{
uint value = fundBalance;
fundBalance = 0;
if (value > 0 && !singularDTVFund.workshop().send(value)) {
throw;
}
checkInvariants();
return true;
}
/// @dev Sets token value in Wei.
/// @param valueInWei New value.
function changeBaseValue(uint valueInWei)
external
noEther
onlyOwner
returns (bool)
{
baseValue = valueInWei;
return true;
}
/// @dev Returns if 1 year passed since beginning of crowdfunding.
function oneYearPassed()
constant
external
noEther
returns (bool)
{
return now - startDate >= TOKEN_LOCKING_PERIOD;
}
/// @dev Returns if campaign ended successfully.
function campaignEndedSuccessfully()
constant
external
noEther
returns (bool)
{
if (stage == Stages.CrowdfundingEndedAndGoalReached) {
return true;
}
return false;
}
// updateStage allows calls to receive correct stage. It can be used for transactions but is not part of the regular crowdfunding routine.
// It is not marked as constant because timedTransitions modifier is altering state and constant is not yet enforced by solc.
/// @dev returns correct stage, even if a function with timedTransitions modifier has not yet been called successfully.
function updateStage()
external
timedTransitions
noEther
returns (Stages)
{
return stage;
}
/// @dev Setup function sets external contracts' addresses.
/// @param singularDTVFundAddress Crowdfunding address.
/// @param singularDTVTokenAddress Token address.
function setup(address singularDTVFundAddress, address singularDTVTokenAddress)
external
onlyOwner
noEther
returns (bool)
{
if (address(singularDTVFund) == 0 && address(singularDTVToken) == 0) {
singularDTVFund = SingularDTVFund(singularDTVFundAddress);
singularDTVToken = SingularDTVToken(singularDTVTokenAddress);
return true;
}
return false;
}
/// @dev Contract constructor function sets owner and start date.
function SingularDTVCrowdfunding() noEther {
// Set owner address
owner = msg.sender;
// Set start-date of crowdfunding
startDate = now;
}
/// @dev Fallback function always fails. Use fund function to fund the contract with Ether.
function () {
throw;
}
}
SingularDTVFund.sol
import "AbstractSingularDTVToken.sol";
import "AbstractSingularDTVCrowdfunding.sol";
/// @title Fund contract - Implements revenue distribution.
/// @author Stefan George - <stefan.george@consensys.net>
contract SingularDTVFund {
/*
* External contracts
*/
SingularDTVToken public singularDTVToken;
SingularDTVCrowdfunding public singularDTVCrowdfunding;
/*
* Storage
*/
address public owner;
address constant public workshop = {{MistWallet}};
uint public totalRevenue;
// User's address => Revenue at time of withdraw
mapping (address => uint) public revenueAtTimeOfWithdraw;
// User's address => Revenue which can be withdrawn
mapping (address => uint) public owed;
/*
* Modifiers
*/
modifier noEther() {
if (msg.value > 0) {
throw;
}
_
}
modifier onlyOwner() {
// Only guard is allowed to do this action.
if (msg.sender != owner) {
throw;
}
_
}
modifier campaignEndedSuccessfully() {
if (!singularDTVCrowdfunding.campaignEndedSuccessfully()) {
throw;
}
_
}
/*
* Contract functions
*/
/// @dev Deposits revenue. Returns success.
function depositRevenue()
external
campaignEndedSuccessfully
returns (bool)
{
totalRevenue += msg.value;
return true;
}
/// @dev Withdraws revenue share for user. Returns revenue share.
/// @param forAddress Shareholder's address.
function calcRevenue(address forAddress) internal returns (uint) {
return singularDTVToken.balanceOf(forAddress) * (totalRevenue - revenueAtTimeOfWithdraw[forAddress]) / singularDTVToken.totalSupply();
}
/// @dev Withdraws revenue share for user. Returns revenue share.
function withdrawRevenue()
external
noEther
returns (uint)
{
uint value = calcRevenue(msg.sender) + owed[msg.sender];
revenueAtTimeOfWithdraw[msg.sender] = totalRevenue;
owed[msg.sender] = 0;
if (value > 0 && !msg.sender.send(value)) {
throw;
}
return value;
}
/// @dev Credits revenue share to owed balance.
/// @param forAddress Shareholder's address.
function softWithdrawRevenueFor(address forAddress)
external
noEther
returns (uint)
{
uint value = calcRevenue(forAddress);
revenueAtTimeOfWithdraw[forAddress] = totalRevenue;
owed[forAddress] += value;
return value;
}
/// @dev Setup function sets external contracts' addresses.
/// @param singularDTVTokenAddress Token address.
function setup(address singularDTVCrowdfundingAddress, address singularDTVTokenAddress)
external
noEther
onlyOwner
returns (bool)
{
if (address(singularDTVCrowdfunding) == 0 && address(singularDTVToken) == 0) {
singularDTVCrowdfunding = SingularDTVCrowdfunding(singularDTVCrowdfundingAddress);
singularDTVToken = SingularDTVToken(singularDTVTokenAddress);
return true;
}
return false;
}
/// @dev Contract constructor function sets guard and initial token balances.
function SingularDTVFund() noEther {
// Set owner address
owner = msg.sender;
}
}
SingularDTVToken.sol
import "StandardToken.sol";
import "AbstractSingularDTVFund.sol";
import "AbstractSingularDTVCrowdfunding.sol";
/// @title Token contract - Implements token issuance.
/// @author Stefan George - <stefan.george@consensys.net>
contract SingularDTVToken is StandardToken {
/*
* External contracts
*/
SingularDTVFund constant singularDTVFund = SingularDTVFund({{SingularDTVFund}});
SingularDTVCrowdfunding constant singularDTVCrowdfunding = SingularDTVCrowdfunding({{SingularDTVCrowdfunding}});
/*
* Token meta data
*/
string constant public name = "SingularDTV";
string constant public symbol = "SNGLS";
uint8 constant public decimals = 0;
/*
* Modifiers
*/
modifier noEther() {
if (msg.value > 0) {
throw;
}
_
}
modifier workshopWaitedOneYear() {
// Workshop can only transfer shares after a two years period.
if (msg.sender == singularDTVFund.workshop() && !singularDTVCrowdfunding.oneYearPassed()) {
throw;
}
_
}
modifier isCrowdfundingContract () {
// Only crowdfunding contract is allowed to proceed.
if (msg.sender != address(singularDTVCrowdfunding)) {
throw;
}
_
}
/*
* Contract functions
*/
/// @dev Crowdfunding contract issues new tokens for address. Returns success.
/// @param _for Address of receiver.
/// @param tokenCount Number of tokens to issue.
function issueTokens(address _for, uint tokenCount)
external
isCrowdfundingContract
returns (bool)
{
if (tokenCount == 0) {
return false;
}
balances[_for] += tokenCount;
totalSupply += tokenCount;
return true;
}
/// @dev Transfers sender's tokens to a given address. Returns success.
/// @param to Address of token receiver.
/// @param value Number of tokens to transfer.
function transfer(address to, uint256 value)
noEther
workshopWaitedOneYear
returns (bool)
{
// Both parties withdraw their revenue first
singularDTVFund.softWithdrawRevenueFor(msg.sender);
singularDTVFund.softWithdrawRevenueFor(to);
return super.transfer(to, value);
}
/// @dev Allows allowed third party to transfer tokens from one address to another. Returns success.
/// @param from Address from where tokens are withdrawn.
/// @param to Address to where tokens are sent.
/// @param value Number of tokens to transfer.
function transferFrom(address from, address to, uint256 value)
noEther
workshopWaitedOneYear
returns (bool)
{
// Both parties withdraw their revenue first
singularDTVFund.softWithdrawRevenueFor(from);
singularDTVFund.softWithdrawRevenueFor(to);
return super.transferFrom(from, to, value);
}
/// @dev Contract constructor function sets initial token balances.
function SingularDTVToken() noEther {
// Set initial share distribution
balances[singularDTVFund.workshop()] = 400000000; // ~400M
// Series A investors
balances[0x0196b712a0459cbee711e7c1d34d2c85a9910379] = 5000000;
balances[0x0f94dc84ce0f5fa2a8cc8d27a6969e25b5a39273] = 200000;
balances[0x122b7eb5f629d806c8adb0baa0560266abb3ec80] = 450000;
balances[0x13870d30fcdb7d7ae875668f2a1219225295d57c] = 50000;
balances[0x26640e826547bc700b8c7a9cc2c1c39a4ab3cbb3] = 900000;
balances[0x26bbfc6b23bc36e84447f061c6804f3a8b1a3698] = 250000;
balances[0x2d37383a45b5122a27efade69f7180eee4d965da] = 1270000;
balances[0x2e79b81121193d55c4934c0f32ad3d0474ca7b9c] = 4200000;
balances[0x3114844fc0e3de03963bbd1d983ba17ca89ad010] = 5000000;
balances[0x378e6582e4e3723f7076c7769eef6febf51258e1] = 680000;
balances[0x3e18530a4ee49a0357ffc8e74c08bfdee3915482] = 2490000;
balances[0x43fed1208d25ca0ef5681a5c17180af50c19f826] = 100000;
balances[0x4f183b18302c0ac5804b8c455018efc51af15a56] = 10000;
balances[0x55a886834658ccb6f26c39d5fdf6d833df3a276a] = 100000;
balances[0x5faa1624422db662c654ab35ce57bf3242888937] = 5000000;
balances[0x6407b662b306e2353b627488da952337a5a0bbaa] = 5000000;
balances[0x66c334fff8c8b8224b480d8da658ca3b032fe625] = 10000000;
balances[0x6c24991c6a40cd5ad6fab78388651fb324b35458] = 250000;
balances[0x781ba492f786b2be48c2884b733874639f50022c] = 500000;
balances[0x79b48f6f1ac373648c509b74a2c04a3281066457] = 2000000;
balances[0x835898804ed30e20aa29f2fe35c9f225175b049f] = 100000;
balances[0x93c56ea8848150389e0917de868b0a23c87cf7b1] = 2790000;
balances[0x93f959df3df3c6ee01ee9748327b881b2137bf2a] = 450000;
balances[0x9adc0215372e4ffd8c89621a6bd9cfddf230349f] = 550000;
balances[0xae4dbd3dae66722315541d66fe9457b342ac76d9] = 500000;
balances[0xbae02fe006f115e45b372f2ddc053eedca2d6fff] = 1800000;
balances[0xcc835821f643e090d8157de05451b416cd1202c4] = 300000;
balances[0xce75342b92a7d0b1a2c6e9835b6b85787e12e585] = 670000;
balances[0xd2b388467d9d0c30bab0a68070c6f49c473583a0] = 990000;
balances[0xdca0724ddde95bbace1b557cab4375d9a813da49] = 3500000;
balances[0xe3ef62165b60cac0fcbe9c2dc6a03aab4c5c8462] = 150000;
balances[0xe4f7d5083baeea7810b6d816581bb0ee7cd4b6f4] = 10560000;
balances[0xef08eb55d3482973c178b02bd4d5f2cea420325f] = 80000;
balances[0xfdecc9f2ee374cedc94f72ab4da2de896ce58c19] = 5000000;
balances[0xe5ff71dc1dea8cd2552eec59e9a5e8813da9bb01] = 29110000;
totalSupply = 500000000; // 500M
}
}
SingularDTVWeifund.sol
import "AbstractCampaign.sol";
import "AbstractSingularDTVFund.sol";
import "AbstractSingularDTVCrowdfunding.sol";
/// @title Crowdfunding contract - Implements crowdfunding functionality.
/// @author Stefan George - <stefan.george@consensys.net>
contract SingularDTVWeifund is Campaign {
/*
* External contracts
*/
SingularDTVFund constant singularDTVFund = SingularDTVFund({{SingularDTVFund}});
SingularDTVCrowdfunding constant singularDTVCrowdfunding = SingularDTVCrowdfunding({{SingularDTVCrowdfunding}});
string constant public name = "SingularDTV Campaign";
string constant public contributeMethodABI = "fund()";
string constant public refundMethodABI = "withdrawFunding()";
string constant public payoutMethodABI = "withdrawForWorkshop()";
/// @notice use to determine the beneficiary destination for the campaign
/// @return the beneficiary address that will receive the campaign payout
function beneficiary() constant returns(address) {
return singularDTVFund.workshop();
}
/// @notice the time at which the campaign fails or succeeds
/// @return the uint unix timestamp at which time the campaign expires
function expiry() constant returns(uint256 timestamp) {
return singularDTVCrowdfunding.startDate() + singularDTVCrowdfunding.CROWDFUNDING_PERIOD();
}
/// @notice the goal the campaign must reach in order for it to succeed
/// @return the campaign funding goal specified in wei as a uint256
function fundingGoal() constant returns(uint256 amount) {
return singularDTVCrowdfunding.TOKEN_TARGET() * singularDTVCrowdfunding.valuePerShare();
}
/// @notice the goal the campaign must reach in order for it to succeed
/// @return the campaign funding goal specified in wei as a uint256
function amountRaised() constant returns(uint256 amount) {
return singularDTVCrowdfunding.fundBalance();
}
}
StandardToken.sol
import "AbstractToken.sol";
contract StandardToken is Token {
/*
* Data structures
*/
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
uint256 public totalSupply;
/*
* Read and write storage functions
*/
/// @dev Transfers sender's tokens to a given address. Returns success.
/// @param _to Address of token receiver.
/// @param _value Number of tokens to transfer.
function transfer(address _to, uint256 _value) returns (bool success) {
if (balances[msg.sender] >= _value && _value > 0) {
balances[msg.sender] -= _value;
balances[_to] += _value;
Transfer(msg.sender, _to, _value);
return true;
}
else {
return false;
}
}
/// @dev Allows allowed third party to transfer tokens from one address to another. Returns success.
/// @param _from Address from where tokens are withdrawn.
/// @param _to Address to where tokens are sent.
/// @param _value Number of tokens to transfer.
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
Transfer(_from, _to, _value);
return true;
}
else {
return false;
}
}
/// @dev Returns number of tokens owned by given address.
/// @param _owner Address of token owner.
function balanceOf(address _owner) constant returns (uint256 balance) {
return balances[_owner];
}
/// @dev Sets approved amount of tokens for spender. Returns success.
/// @param _spender Address of allowed account.
/// @param _value Number of approved tokens.
function approve(address _spender, uint256 _value) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
/*
* Read storage functions
*/
/// @dev Returns number of allowed tokens for given address.
/// @param _owner Address of token owner.
/// @param _spender Address of token spender.
function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
}