Last active
September 29, 2017 13:58
-
-
Save computerphysicslab/9673ba183d7d9674192c924545da7c77 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Watafan-Smart-Contracts-1.0.sol | |
/* | |
Watafan Smart Contracts v1.0 | |
developed by: | |
MarketPay.io , 2017 | |
https://marketpay.io/ | |
http://lnked.in/blockchain | |
v1.0 https://gist.github.com/computerphysicslab/9673ba183d7d9674192c924545da7c77 | |
+ Four contracts together: tokens, ICO, issuers, assets | |
*/ | |
pragma solidity ^0.4.11; | |
contract System { | |
address owner; | |
// @notice To limit functions usage to contract owner | |
modifier onlyOwner() { | |
if (msg.sender != owner) { | |
Error('Mortal: onlyOwner function called by user that is not owner'); | |
} else { | |
_; | |
} | |
} | |
// @notice For debugging purposes when using solidity online browser | |
function whoAmI() public constant returns (address) { | |
return msg.sender; | |
} | |
// @notice Get the current timestamp from last mined block | |
function timestamp() public constant returns (uint256) { | |
return block.timestamp; | |
} | |
function System() public { | |
// This is the constructor, so owner should be equal to msg.sender, and this method should be called just once | |
owner = msg.sender; | |
// make sure owner address is configured | |
// if(owner == 0x0) throw; | |
// owner address can call this function | |
// if (msg.sender != owner ) throw; | |
} | |
// **** EVENTS | |
// @notice A generic error log | |
event Error(string error); | |
} | |
contract Tokens is System { | |
// **** DATA | |
mapping (address => uint256) balances; | |
mapping (address => mapping (address => uint256)) allowed; | |
uint256 public initialSupply; // Initial and total token supply | |
uint256 public totalSupply; | |
// bool allocated = false; // True after defining token parameters and initial mint | |
// Public variables of the token, all used for display | |
// HumanStandardToken is a specialisation of ERC20 defining these parameters | |
string public name; | |
string public symbol; | |
uint8 public decimals; | |
string public standard = 'H0.1'; | |
// **** METHODS | |
// Get total amount of tokens, totalSupply is a public var actually | |
// function totalSupply() public constant returns (uint256 totalSupply) {} | |
// Get the account balance of another account with address _owner | |
function balanceOf(address _owner) public constant returns (uint256 balance) { | |
return balances[_owner]; | |
} | |
// Send _amount amount of tokens to address _to | |
function transfer(address _to, uint256 _amount) public returns (bool success) { | |
if (balances[msg.sender] < _amount) { | |
Error('transfer: the amount to transfer is higher than your token balance'); | |
return false; | |
} | |
balances[msg.sender] -= _amount; | |
balances[_to] += _amount; | |
Transfer(msg.sender, _to, _amount); | |
return true; | |
} | |
// Send _amount amount of tokens from address _from to address _to | |
// The transferFrom method is used for a withdraw workflow, allowing contracts to send | |
// tokens on your behalf, for example to "deposit" to a contract address and/or to charge | |
// fees in sub-currencies; the command should fail unless the _from account has | |
// deliberately authorized the sender of the message via some mechanism | |
function transferFrom(address _from, address _to, uint256 _amount) public returns (bool success) { | |
if (balances[_from] < _amount) { | |
Error('transfer: the amount to transfer is higher than the token balance of the source'); | |
return false; | |
} | |
if (allowed[_from][msg.sender] < _amount) { | |
Error('transfer: the amount to transfer is higher than the maximum token transfer allowed by the source'); | |
return false; | |
} | |
balances[_from] -= _amount; | |
balances[_to] += _amount; | |
allowed[_from][msg.sender] -= _amount; | |
Transfer(_from, _to, _amount); | |
return true; | |
} | |
// Allow _spender to withdraw from your account, multiple times, up to the _amount amount. | |
// If this function is called again it overwrites the current allowance with _amount. | |
function approve(address _spender, uint256 _amount) public returns (bool success) { | |
allowed[msg.sender][_spender] = _amount; | |
Approval(msg.sender, _spender, _amount); | |
return true; | |
} | |
// Returns the amount which _spender is still allowed to withdraw from _owner | |
function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { | |
return allowed[_owner][_spender]; | |
} | |
// Constructor: set up token properties and owner token balance | |
function Tokens() public { | |
// call this function just once | |
// if (allocated) throw; | |
initialSupply = 100000000 * 1000; // 100M tokens, 3 decimals | |
totalSupply = initialSupply; | |
name = "Watafan"; | |
symbol = "FAN"; | |
decimals = 3; | |
balances[owner] = totalSupply; | |
Transfer(this, owner, totalSupply); | |
// allocated = true; | |
} | |
// **** EVENTS | |
// Triggered when tokens are transferred | |
event Transfer(address indexed _from, address indexed _to, uint256 _amount); | |
// Triggered whenever approve(address _spender, uint256 _amount) is called | |
event Approval(address indexed _owner, address indexed _spender, uint256 _amount); | |
} | |
// Minimal interface of ERC20 token contract, just to cast the contract address and make it callable from the ICO and Assets contracts | |
contract IFerc20Tokens { | |
function balanceOf(address _owner) public constant returns (uint256 balance); | |
function transfer(address _to, uint256 _amount) public returns (bool success); | |
function transferFrom(address _from, address _to, uint256 _amount) public returns (bool success); | |
function allowance(address _owner, address _spender) public constant returns (uint256 remaining); | |
} | |
contract ICO is System { | |
// The token being sold | |
IFerc20Tokens public SCTokens; | |
// start and end timestamps where investments are allowed (both inclusive) | |
uint256 public startTime; | |
uint256 public endTime; | |
// address where funds are collected | |
address public wallet; | |
// how many token units a buyer gets per wei | |
// uint256 public rate; | |
uint256 public weisPerToken; | |
// amount of raised money in wei | |
uint256 public weiRaised; | |
event TokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount); | |
function ICO(address _SCTokens) public { | |
startTime = timestamp(); | |
endTime = timestamp() + 10000; | |
// rate = 1; | |
// 1 ether = 1000000000000000000 weis | |
// 1 ether = 1000 euros | |
// 1 euro = 1000000000000000 weis | |
// 1 token = 1 euro | |
// 1 token = 1000000000000000 weis | |
// 1 token (3 decimals) = 1000000000000 weis | |
weisPerToken = 1000000000000; | |
wallet = 0x0034bd0dcc942221d12e331dcc9d67ec869dd516e9; | |
SCTokens = IFerc20Tokens(_SCTokens); | |
} | |
// fallback function can be used to buy tokens | |
function () payable public { | |
buyTokens(msg.sender); | |
} | |
// low level token purchase function | |
function buyTokens(address beneficiary) public payable returns (bool) { | |
if (beneficiary == 0x0) { | |
Error('buyTokens: beneficiary == 0x0'); | |
return false; | |
} | |
bool withinPeriod = timestamp() >= startTime && timestamp() <= endTime; | |
if (!withinPeriod) { | |
Error('buyTokens: Not withinPeriod'); | |
return false; | |
} | |
bool nonZeroPurchase = msg.value != 0; | |
if (!nonZeroPurchase) { | |
Error('buyTokens: ZeroPurchase'); | |
return false; | |
} | |
uint256 weiAmount = msg.value; | |
// calculate token amount to be created | |
// uint256 tokens = weiAmount * rate; | |
uint256 tokenAmount = weiAmount / weisPerToken; | |
// update state | |
weiRaised = weiRaised + weiAmount; | |
SCTokens.transfer(beneficiary, tokenAmount); | |
TokenPurchase(msg.sender, beneficiary, weiAmount, tokenAmount); | |
forwardFunds(); | |
return true; | |
} | |
// send ether to the fund collection wallet | |
// override to create custom fund forwarding mechanisms | |
function forwardFunds() internal { | |
wallet.transfer(msg.value); | |
} | |
// @return true if crowdsale event has ended | |
function hasEnded() public constant returns (bool) { | |
return timestamp() > endTime; | |
} | |
} | |
contract Issuers is System { | |
// **** DATA | |
struct issu { | |
uint256 issuerId; | |
bool issuerAuth; | |
address issuerAddress; | |
string issuerContent; // a JSON object containing data about the issuer | |
} | |
mapping (address => issu) issuerData; | |
mapping (uint256 => address) issuerAddressById; // indexed issuers so as to be full scannable | |
uint256 lastId; | |
// **** METHODS | |
// Checks whether a given user is an authorized issuer | |
function isIssuer(address _issuer) public constant returns (bool) { | |
return (issuerData[_issuer].issuerAuth); | |
} | |
function newIssuer(address _issuer, string _content) internal onlyOwner returns (uint256 id) { | |
// Update Index | |
id = lastId + 1; | |
issuerData[_issuer].issuerId = id; | |
issuerData[_issuer].issuerAuth = false; | |
issuerData[_issuer].issuerAddress = _issuer; | |
issuerData[_issuer].issuerContent = _content; | |
issuerAddressById[id] = _issuer; | |
lastId = lastId + 1; | |
NewIssuer(_issuer, id, timestamp()); // Event log | |
} | |
function grantIssuer(address _issuer, string _content) external onlyOwner { | |
// Checks whether this user has been previously added as an issuer | |
uint256 id; | |
if (issuerData[_issuer].issuerId > 0) { | |
id = issuerData[_issuer].issuerId; | |
} else { | |
id = newIssuer(_issuer, _content); | |
} | |
issuerData[_issuer].issuerAuth = true; | |
GrantIssuer(_issuer, id, timestamp()); // Event log | |
} | |
function revokeIssuer(address _issuer) external onlyOwner { | |
issuerData[_issuer].issuerAuth = false; | |
RevokeIssuer(_issuer, timestamp()); // Event log | |
} | |
// Queries the issuer, knowing the id | |
function getIssuerById(uint256 id) public constant returns (uint256 _issuerId, bool _issuerAuth, address _issuerAddress, string _issuerContent) { | |
address _issuer = issuerAddressById[id]; | |
return (issuerData[_issuer].issuerId, issuerData[_issuer].issuerAuth, issuerData[_issuer].issuerAddress, issuerData[_issuer].issuerContent); | |
} | |
// **** EVENTS | |
// Triggered when a new issuer is created | |
event NewIssuer(address who, uint256 _id, uint256 _timestamp); | |
// Triggered when a user is granted to become an issuer | |
event GrantIssuer(address who, uint256 _id, uint256 _timestamp); | |
// Triggered when a user is revoked for being an issuer | |
event RevokeIssuer(address who, uint256 _timestamp); | |
} | |
// Interface of issuer contract, just to cast the contract address and make it callable from the asset contract | |
contract IFIssuers { | |
function isIssuer(address _issuer) public constant returns (bool); | |
} | |
contract Assets is System { | |
// **** DATA | |
/** Asset states | |
* | |
* - Released: Once issued the asset stays as released until sent for free to someone specified by issuer | |
* - ForSale: The asset belongs to a user and is open to be sold | |
* - Unfungible: The asset cannot be sold, remaining to the user it belongs to. | |
*/ | |
enum assetStatus { Released, ForSale, Unfungible } | |
// https://ethereum.stackexchange.com/questions/1807/enums-in-solidity | |
struct asst { | |
uint256 assetId; | |
address assetOwner; | |
address issuer; | |
string content; // a JSON object containing the image data of the asset and its title | |
uint256 sellPrice; // in Watafan tokens, how many of them for this asset | |
assetStatus status; // behaviour (tradability) of the asset depends upon its status | |
} | |
mapping (uint256 => asst) assetsById; | |
uint256 lastAssetId; // Last assetId | |
uint256 assetFeeIssuer; // Fee percentage for Issuer on every asset sale transaction | |
uint256 assetFeeWatafan; // Fee percentage for Watafan on every asset sale transaction | |
IFerc20Tokens public SCTokens; // The token used to pay for the assets | |
IFIssuers public SCIssuers; // Contract that defines who is an issuer and who is not | |
// **** METHODS | |
// Constructor | |
function Assets(address _SCTokens, address _SCIssuers) public { | |
SCTokens = IFerc20Tokens(_SCTokens); | |
SCIssuers = IFIssuers(_SCIssuers); | |
assetFeeIssuer = 25; // default fee | |
assetFeeWatafan = 25; // default fee | |
} | |
// Queries the asset, knowing the id | |
function getAssetById(uint256 assetId) public constant returns (uint256 _assetId, address _assetOwner, address _issuer, string _content, uint256 _sellPrice, uint256 _status) { | |
return (assetsById[assetId].assetId, assetsById[assetId].assetOwner, assetsById[assetId].issuer, assetsById[assetId].content, assetsById[assetId].sellPrice, uint256(assetsById[assetId].status)); | |
} | |
// Seller sends an owned asset to a buyer, providing its allowance matches token price and transfer the tokens from buyer | |
function sendAssetTo(uint256 assetId, address assetBuyer) public returns (bool) { | |
// assetId must not be zero | |
if (assetId == 0) { | |
Error('sendAssetTo: assetId must not be zero'); | |
return false; | |
} | |
// Check whether the asset belongs to the seller | |
if (assetsById[assetId].assetOwner != msg.sender) { | |
Error('sendAssetTo: the asset does not belong to you, the seller'); | |
return false; | |
} | |
if (assetsById[assetId].sellPrice > 0) { // for non-null token paid transactions | |
// Check whether there is balance enough from the buyer to get its tokens | |
if (SCTokens.balanceOf(assetBuyer) < assetsById[assetId].sellPrice) { | |
Error('sendAssetTo: there is not enough balance from the buyer to get its tokens'); | |
return false; | |
} | |
// Check whether there is allowance enough from the buyer to get its tokens | |
if (SCTokens.allowance(assetBuyer, msg.sender) < assetsById[assetId].sellPrice) { | |
Error('sendAssetTo: there is not enough allowance from the buyer to get its tokens'); | |
return false; | |
} | |
// Get the buyer tokens | |
if (!SCTokens.transferFrom(assetBuyer, msg.sender, assetsById[assetId].sellPrice)) { | |
Error('sendAssetTo: transferFrom failed'); // This shouldn't happen ever, but just in case... | |
return false; | |
} | |
} | |
// Set the asset status to Unfungible | |
assetsById[assetId].status = assetStatus.Unfungible; | |
// Transfer the asset to the buyer | |
assetsById[assetId].assetOwner = assetBuyer; | |
// Event log | |
SendAssetTo(assetId, assetBuyer); | |
return true; | |
} | |
// Buyer gets an asset providing it is in ForSale status, and pays the corresponding tokens to the seller/owner. amount must match assetPrice to have a deal. | |
function buyAsset(uint256 assetId, uint256 amount) public returns (bool) { | |
// assetId must not be zero | |
if (assetId == 0) { | |
Error('buyAsset: assetId must not be zero'); | |
return false; | |
} | |
// Check whether the asset is in ForSale status | |
if (assetsById[assetId].status != assetStatus.ForSale) { | |
Error('buyAsset: the asset is not for sale'); | |
return false; | |
} | |
// Check whether the asset price is the same as amount | |
if (assetsById[assetId].sellPrice != amount) { | |
Error('buyAsset: the asset price does not match the specified amount'); | |
return false; | |
} | |
if (assetsById[assetId].sellPrice > 0) { // for non-null token paid transactions | |
// Check whether there is balance enough from the buyer to pay the asset | |
if (SCTokens.balanceOf(msg.sender) < assetsById[assetId].sellPrice) { | |
Error('buyAsset: there is not enough token balance to buy this asset'); | |
return false; | |
} | |
// Calculate the seller income | |
uint256 sellerIncome = assetsById[assetId].sellPrice * (1000 - assetFeeIssuer - assetFeeWatafan) / 1000; | |
// Send the buyer's tokens to the seller | |
if (!SCTokens.transfer(assetsById[assetId].assetOwner, sellerIncome)) { | |
Error('buyAsset: seller token transfer failed'); // This shouldn't happen ever, but just in case... | |
return false; | |
} | |
// Send the issuer's fee | |
uint256 issuerIncome = assetsById[assetId].sellPrice * assetFeeIssuer / 1000; | |
if (!SCTokens.transfer(assetsById[assetId].issuer, issuerIncome)) { | |
Error('buyAsset: issuer token transfer failed'); // This shouldn't happen ever, but just in case... | |
return false; | |
} | |
// Send the Watafan's fee | |
uint256 watafanIncome = assetsById[assetId].sellPrice * assetFeeWatafan / 1000; | |
if (!SCTokens.transfer(owner, watafanIncome)) { | |
Error('buyAsset: watafan token transfer failed'); // This shouldn't happen ever, but just in case... | |
return false; | |
} | |
} | |
// Set the asset status to Unfungible | |
assetsById[assetId].status = assetStatus.Unfungible; | |
// Transfer the asset to the buyer | |
assetsById[assetId].assetOwner = msg.sender; | |
// Event log | |
BuyAsset(assetId, amount); | |
return true; | |
} | |
// To limit issue functions just to authorized issuers | |
modifier onlyIssuer() { | |
if (!SCIssuers.isIssuer(msg.sender)) { | |
Error('onlyIssuer function called by user that is not an authorized issuer'); | |
} else { | |
_; | |
} | |
} | |
// To be called by issueAssetTo() and properly authorized issuers | |
function issueAsset(string content, uint256 sellPrice) onlyIssuer internal returns (uint256 nextAssetId) { | |
// Find out next asset Id | |
nextAssetId = lastAssetId + 1; | |
assetsById[nextAssetId].assetId = nextAssetId; | |
assetsById[nextAssetId].assetOwner = msg.sender; | |
assetsById[nextAssetId].issuer = msg.sender; | |
assetsById[nextAssetId].content = content; | |
assetsById[nextAssetId].sellPrice = sellPrice; | |
assetsById[nextAssetId].status = assetStatus.Released; | |
// Update lastAssetId | |
lastAssetId++; | |
// Event log | |
IssueAsset(nextAssetId, msg.sender, sellPrice); | |
return nextAssetId; | |
} | |
// Issuer sends a new free asset to a given user as a gift | |
function issueAssetTo(string content, address to) public returns (bool) { | |
uint256 assetId = issueAsset(content, 0); // 0 tokens, as a gift | |
if (assetId == 0) { | |
Error('issueAssetTo: asset has not been properly issued'); | |
return (false); | |
} | |
// The brand new asset is inmediatly sent to the recipient | |
return(sendAssetTo(assetId, to)); | |
} | |
// Seller can block tradability of its assets | |
function setAssetUnfungible(uint256 assetId) public returns (bool) { | |
// assetId must not be zero | |
if (assetId == 0) { | |
Error('setAssetUnfungible: assetId must not be zero'); | |
return false; | |
} | |
// Check whether the asset belongs to the caller | |
if (assetsById[assetId].assetOwner != msg.sender) { | |
Error('setAssetUnfungible: only owners of the asset are allowed to update its status'); | |
return false; | |
} | |
assetsById[assetId].status = assetStatus.Unfungible; | |
// Event log | |
SetAssetUnfungible(assetId, msg.sender); | |
return true; | |
} | |
// Seller updates the price of its assets and its status to ForSale | |
function setAssetPrice(uint256 assetId, uint256 sellPrice) public returns (bool) { | |
// assetId must not be zero | |
if (assetId == 0) { | |
Error('setAssetPrice: assetId must not be zero'); | |
return false; | |
} | |
// Check whether the asset belongs to the caller | |
if (assetsById[assetId].assetOwner != msg.sender) { | |
Error('setAssetPrice: only owners of the asset are allowed to set its price and update its status'); | |
return false; | |
} | |
assetsById[assetId].sellPrice = sellPrice; | |
assetsById[assetId].status = assetStatus.ForSale; | |
// Event log | |
SetAssetPrice(assetId, msg.sender, sellPrice); | |
return true; | |
} | |
// Owner updates the fees for assets sale transactions | |
function setAssetSaleFees(uint256 feeIssuer, uint256 feeWatafan) public onlyOwner returns (bool) { | |
// Check new fees are consistent | |
if (feeIssuer + feeWatafan > 1000) { | |
Error('setAssetSaleFees: added fees exceed 100.0%. Not updated.'); | |
return false; | |
} | |
assetFeeIssuer = feeIssuer; | |
assetFeeWatafan = feeWatafan; | |
// Event log | |
SetAssetSaleFees(feeIssuer, feeWatafan); | |
return true; | |
} | |
// **** EVENTS | |
// Triggered when a seller sends its asset to a buyer and receives the corresponding tokens | |
event SendAssetTo(uint256 assetId, address assetBuyer); | |
// Triggered when a buyer sends its tokens to a seller and receives the specified asset | |
event BuyAsset(uint256 assetId, uint256 amount); | |
// Triggered when the admin issues a new asset | |
event IssueAsset(uint256 nextAssetId, address assetOwner, uint256 sellPrice); | |
// Triggered when the user updates its asset status to Unfungible | |
event SetAssetUnfungible(uint256 assetId, address assetOwner); | |
// Triggered when the user updates its asset price and status to ForSale | |
event SetAssetPrice(uint256 assetId, address assetOwner, uint256 sellPrice); | |
// Triggered when the owner updates the asset sale fees | |
event SetAssetSaleFees(uint256 feeIssuer, uint256 feeWatafan); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment