Skip to content

Instantly share code, notes, and snippets.

@nachoalthabe
Created August 3, 2018 00:43
Show Gist options
  • Save nachoalthabe/02bf8feba322504476320ceade4ceaa4 to your computer and use it in GitHub Desktop.
Save nachoalthabe/02bf8feba322504476320ceade4ceaa4 to your computer and use it in GitHub Desktop.
solc version: 0.4.25-develop.2018.6.6+commit.59b35fa5.Linux.g++
pragma solidity ^0.4.13;
contract OracleInterface {
struct PriceData {
uint ARTTokenPrice;
uint blockHeight;
}
mapping(uint => PriceData) public historicPricing;
uint public index;
address public owner;
uint8 public decimals;
function setPrice(uint price) public returns (uint _index) {}
function getPrice() public view returns (uint price, uint _index, uint blockHeight) {}
function getHistoricalPrice(uint _index) public view returns (uint price, uint blockHeight) {}
event Updated(uint indexed price, uint indexed index);
}
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
contract ERC20Interface is ERC20Basic {
uint8 public decimals;
}
contract HasNoTokens {
/**
* @dev Reject all ERC223 compatible tokens
* @param from_ address The address that is transferring the tokens
* @param value_ uint256 the amount of the specified token
* @param data_ Bytes The data passed from the caller.
*/
function tokenFallback(address from_, uint256 value_, bytes data_) external {
from_;
value_;
data_;
revert();
}
}
contract Ownable {
address public owner;
event OwnershipRenounced(address indexed previousOwner);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipRenounced(owner);
owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function transferOwnership(address _newOwner) public onlyOwner {
_transferOwnership(_newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function _transferOwnership(address _newOwner) internal {
require(_newOwner != address(0));
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
}
}
contract HasNoEther is Ownable {
/**
* @dev Constructor that rejects incoming Ether
* @dev The `payable` flag is added so we can access `msg.value` without compiler warning. If we
* leave out payable, then Solidity will allow inheriting contracts to implement a payable
* constructor. By doing it this way we prevent a payable constructor from working. Alternatively
* we could use assembly to access msg.value.
*/
constructor() public payable {
require(msg.value == 0);
}
/**
* @dev Disallows direct send by settings a default function without the `payable` flag.
*/
function() external {
}
/**
* @dev Transfer all Ether held by the contract to the owner.
*/
function reclaimEther() external onlyOwner {
owner.transfer(address(this).balance);
}
}
contract DutchAuction is Ownable, HasNoEther, HasNoTokens {
using SafeMath for uint256;
/// @notice Auction Data
uint public min_shares_to_sell;
uint public max_shares_to_sell;
uint public min_share_price;
uint public available_shares;
bool private fundraise_defined;
uint public fundraise_max;
/// @notice Auction status
state public status = state.pending;
enum state { pending, active, ended, decrypted, success, failure }
/// @notice Events
event Started(uint block_number);
event BidAdded(uint index);
event Ended(uint block_number);
event BidDecrypted(uint index, bool it_will_process);
event FundraiseDefined(uint min_share_price, uint max);
event BidBurned(uint index);
event Decrypted(uint blocknumber, uint bids_decrypted, uint bids_burned);
event Computed(uint index, uint share_price, uint shares_count);
event Assigned(uint index, uint shares, uint executed_amout, uint refunded);
event Refunded(uint index, uint refunded);
event Success(uint raised, uint share_price, uint delivered_shares);
event Failure(uint raised, uint share_price);
event Execution(address destination,uint value,bytes data);
event ExecutionFailure(address destination,uint value,bytes data);
/// @notice Token assignment data
uint public final_share_price;
uint public computed_fundraise;
uint public final_fundraise;
uint public computed_shares_sold;
uint public final_shares_sold;
uint public winner_bids;
uint public assigned_bids;
uint public assigned_shares;
/// @notice Bidding data
struct BidData {
uint origin_index;
uint bid_id;
address investor_address;
uint share_price;
uint shares_count;
uint transfer_valuation;
uint transfer_token;
uint asigned_shares_count;
uint executed_amount;
bool closed;
}
uint public bids_sorted_count;
uint public bids_sorted_refunded;
mapping (uint => BidData) public bids_sorted; //Is sorted
uint public bids_burned_count;
mapping (uint => uint) public bids_burned;
uint public bids_ignored_count;
uint public bids_ignored_refunded;
mapping (uint => BidData) public bids_ignored;
uint public bids_decrypted_count;
mapping (uint => uint) public bids_decrypted;
uint private bids_reset_count;
struct Bid {
// https://ethereum.stackexchange.com/questions/3184/what-is-the-cheapest-hash-function-available-in-solidity#3200
bytes32 bid_hash;
uint art_price;
uint art_price_index;
bool exist;
bool is_decrypted;
bool is_burned;
bool will_compute;
}
uint public bids_count;
mapping (uint => Bid) public bids;
uint public bids_computed_cursor;
uint public shares_holders_count;
mapping (uint => address) public shares_holders;
mapping (address => uint) public shares_holders_balance;
/// @notice External dependencies
OracleInterface oracle;
uint public oracle_price_decimals_factor;
ERC20Interface art_token_contract;
uint public decimal_precission_difference_factor;
/// @notice Set up the dutch auction
/// @param _min_shares_to_sell The minimum amount of asset shares to be sold
/// @param _max_shares_to_sell The maximum amount of asset shares to be sold
/// @param _available_shares The total share amount the asset will be divided into
/// @param _oracle Address of the ART/USD price oracle contract
/// @param _art_token_contract Address of the ART token contract
constructor(
uint _min_shares_to_sell,
uint _max_shares_to_sell,
uint _available_shares,
address _oracle,
address _art_token_contract
) public {
require(_max_shares_to_sell > 0);
require(_max_shares_to_sell >= _min_shares_to_sell);
require(_available_shares >= _max_shares_to_sell);
require(_oracle != address(0x0));
owner = msg.sender;
min_shares_to_sell = _min_shares_to_sell;
max_shares_to_sell = _max_shares_to_sell;
available_shares = _available_shares;
oracle = OracleInterface(_oracle);
uint256 oracle_decimals = uint256(oracle.decimals());
oracle_price_decimals_factor = 10**oracle_decimals;
art_token_contract = ERC20Interface(_art_token_contract);
uint256 art_token_decimals = uint256(art_token_contract.decimals());
decimal_precission_difference_factor = 10**(art_token_decimals.sub(oracle_decimals));
}
/// @notice Allows configuration of the final parameters needed for
/// auction end state calculation. This is only allowed once the auction
/// has closed and no more bids can enter
/// @param _min_share_price Minimum price accepted for individual asset shares
/// @param _fundraise_max Maximum cap for fundraised capital
function setFundraiseLimits(uint _min_share_price, uint _fundraise_max) public onlyOwner{
require(!fundraise_defined);
require(_min_share_price > 0);
require(_fundraise_max > 0);
require(status == state.ended);
fundraise_max = _fundraise_max;
min_share_price = _min_share_price;
emit FundraiseDefined(min_share_price,fundraise_max);
fundraise_defined = true;
}
/// @notice Starts the auction
function startAuction() public onlyOwner{
require(status == state.pending);
status = state.active;
emit Started(block.number);
}
/// @notice Ends the auction, preventing new bids from entering
function endAuction() public onlyOwner{
require(status == state.active);
status = state.ended;
emit Ended(block.number);
}
/// @notice Append an encrypted bid to the auction. This allows the contract
/// to keep a count on how many bids it has, while staying ignorant of the
/// bid contents.
function appendEncryptedBid(bytes32 _bid_hash, uint price_index) public onlyOwner returns (uint index){
require(status == state.active);
uint art_price;
uint art_price_blockHeight;
(art_price, art_price_blockHeight) = oracle.getHistoricalPrice(price_index);
bids[bids_count] = Bid(_bid_hash, art_price, price_index, true, false, false, false);
index = bids_count;
emit BidAdded(bids_count++);
}
/// @notice Helper function for calculating a bid's hash.
function getBidHash(uint nonce, uint bid_id, address investor_address, uint share_price, uint shares_count) public pure returns(bytes32) {
return keccak256(abi.encodePacked(nonce, bid_id, investor_address, share_price, shares_count));
}
/// @notice Allows the "burning" of a bid, for cases in which a bid was corrupted and can't be decrypted.
/// "Burnt" bids do not participate in the final calculations for auction participants
/// @param _index Indicates the index of the bid to be burnt
function burnBid(uint _index) public onlyOwner {
require(status == state.ended);
require(bids_sorted_count == 0);
require(bids[_index].exist == true);
require(bids[_index].is_decrypted == false);
require(bids[_index].is_burned == false);
bids_burned[bids_burned_count] = _index;
bids_burned_count++;
bids_decrypted[bids_decrypted_count] = _index;
bids_decrypted_count++;
bids[_index].is_burned = true;
emit BidBurned(_index);
}
/// @notice Appends the bid's data to the contract, for use in the final calculations
/// Once all bids are appended, the auction is locked and changes its state to "decrypted"
/// @dev Bids MUST be appended in order of asset valuation,
/// since the contract relies on off-chain sorting and checks if the order is correct
/// @param _nonce Bid parameter
/// @param _index Bid's index inside the contract
/// @param _bid_id Bid parameter
/// @param _investor_address Bid parameter - address of the bid's originator
/// @param _share_price Bid parameter - estimated value of the asset's share price
/// @param _shares_count Bid parameter - amount of shares bid for
/// @param _transfered_token Bid parameter - amount of ART tokens sent with the bid
function appendDecryptedBid(uint _nonce, uint _index, uint _bid_id, address _investor_address, uint _share_price, uint _shares_count, uint _transfered_token) onlyOwner public {
require(status == state.ended);
require(fundraise_defined);
require(bids[_index].exist == true);
require(bids[_index].is_decrypted == false);
require(bids[_index].is_burned == false);
require(_share_price > 0);
require(_shares_count > 0);
require(_transfered_token >= convert_valuation_to_art(_shares_count.mul(_share_price),bids[_index].art_price));
if (bids_sorted_count > 0){
BidData memory previous_bid_data = bids_sorted[bids_sorted_count-1];
require(_share_price <= previous_bid_data.share_price);
if (_share_price == previous_bid_data.share_price){
require(_index > previous_bid_data.origin_index);
}
}
require(
getBidHash(_nonce, _bid_id,_investor_address,_share_price,_shares_count) == bids[_index].bid_hash
);
uint _transfer_amount = _share_price.mul(_shares_count);
BidData memory bid_data = BidData(_index, _bid_id, _investor_address, _share_price, _shares_count, _transfer_amount, _transfered_token, 0, 0, false);
bids[_index].is_decrypted = true;
if (_share_price >= min_share_price){
bids[_index].will_compute = true;
bids_sorted[bids_sorted_count] = bid_data;
bids_sorted_count++;
emit BidDecrypted(_index,true);
}else{
bids[_index].will_compute = false;
bids_ignored[bids_ignored_count] = bid_data;
bids_ignored_count++;
emit BidDecrypted(_index,false);
}
bids_decrypted[bids_decrypted_count] = _index;
bids_decrypted_count++;
if(bids_decrypted_count == bids_count){
emit Decrypted(block.number, bids_decrypted_count.sub(bids_burned_count), bids_burned_count);
status = state.decrypted;
}
}
/// @notice Allows appending multiple decrypted bids (in order) at once.
/// @dev Parameters are the same as appendDecryptedBid but in array format.
function appendDecryptedBids(uint[] _nonce, uint[] _index, uint[] _bid_id, address[] _investor_address, uint[] _share_price, uint[] _shares_count, uint[] _transfered_token) public onlyOwner {
require(_nonce.length == _index.length);
require(_index.length == _bid_id.length);
require(_bid_id.length == _investor_address.length);
require(_investor_address.length == _share_price.length);
require(_share_price.length == _shares_count.length);
require(_shares_count.length == _transfered_token.length);
require(bids_count.sub(bids_decrypted_count) > 0);
for (uint i = 0; i < _index.length; i++){
appendDecryptedBid(_nonce[i], _index[i], _bid_id[i], _investor_address[i], _share_price[i], _shares_count[i], _transfered_token[i]);
}
}
/// @notice Allows resetting the entire bid decryption/appending process
/// in case a mistake was made and it is not possible to continue appending further bids.
function resetAppendDecryptedBids(uint _count) public onlyOwner{
require(status == state.ended);
require(bids_decrypted_count > 0);
require(_count > 0);
if (bids_reset_count == 0){
bids_reset_count = bids_decrypted_count;
}
uint count = _count;
if(bids_reset_count < count){
count = bids_reset_count;
}
do {
bids_reset_count--;
bids[bids_decrypted[bids_reset_count]].is_decrypted = false;
bids[bids_decrypted[bids_reset_count]].is_burned = false;
bids[bids_decrypted[bids_reset_count]].will_compute = false;
count--;
} while(count > 0);
if (bids_reset_count == 0){
bids_sorted_count = 0;
bids_ignored_count = 0;
bids_decrypted_count = 0;
bids_burned_count = 0;
}
}
/// @notice Performs the computation of auction winners and losers.
/// Also, determines if the auction is successful or failed.
/// Bids which place the asset valuation below the minimum fundraise cap
/// as well as bids below the final valuation are marked as ignored or "loser" respectively
/// and do not count towards the process.
/// @dev Since this function is resource intensive, computation is done in batches
/// of `_count` bids, so as to not encounter an OutOfGas exception in the middle
/// of the process.
/// @param _count Amount of bids to be processed in this run.
function computeBids(uint _count) public onlyOwner{
require(status == state.decrypted);
require(_count > 0);
uint count = _count;
// No bids
if (bids_sorted_count == 0){
status = state.failure;
emit Failure(0, 0);
return;
}
//bids_computed_cursor: How many bid already processed
//bids_sorted_count: How many bids can compunte
require(bids_computed_cursor < bids_sorted_count);
//bid: Auxiliary variable
BidData memory bid;
do{
//bid: Current bid to compute
bid = bids_sorted[bids_computed_cursor];
//if only one share of current bid leave us out of fundraise limitis, ignore the bid
//computed_shares_sold: Sumarize shares sold
if (bid.share_price.mul(computed_shares_sold).add(bid.share_price) > fundraise_max){
if(bids_computed_cursor > 0){
bids_computed_cursor--;
}
bid = bids_sorted[bids_computed_cursor];
break;
}
//computed_shares_sold: Sumarize cumpued shares
computed_shares_sold = computed_shares_sold.add(bid.shares_count);
//computed_fundraise: Sumarize fundraise
computed_fundraise = bid.share_price.mul(computed_shares_sold);
emit Computed(bid.origin_index, bid.share_price, bid.shares_count);
//Next bid
bids_computed_cursor++;
count--;
}while(
count > 0 && //We have limite to compute
bids_computed_cursor < bids_sorted_count && //We have more bids to compute
(
computed_fundraise < fundraise_max && //Fundraise is more or equal to max
computed_shares_sold < max_shares_to_sell //Assigned shares are more or equal to max
)
);
if (
bids_computed_cursor == bids_sorted_count || //All bids computed
computed_fundraise >= fundraise_max ||//Fundraise is more or equal to max
computed_shares_sold >= max_shares_to_sell//Max shares raised
){
final_share_price = bid.share_price;
//More than max shares
if(computed_shares_sold >= max_shares_to_sell){
computed_shares_sold = max_shares_to_sell;//Limit shares
computed_fundraise = final_share_price.mul(computed_shares_sold);
winner_bids = bids_computed_cursor;
status = state.success;
emit Success(computed_fundraise, final_share_price, computed_shares_sold);
return;
}
//Max fundraise is raised
if(computed_fundraise.add(final_share_price.mul(1)) >= fundraise_max){//More than max fundraise
computed_fundraise = fundraise_max;//Limit fundraise
winner_bids = bids_computed_cursor;
status = state.success;
emit Success(computed_fundraise, final_share_price, computed_shares_sold);
return;
}
//All bids computed
if (bids_computed_cursor == bids_sorted_count){
if (computed_shares_sold >= min_shares_to_sell){
winner_bids = bids_computed_cursor;
status = state.success;
emit Success(computed_fundraise, final_share_price, computed_shares_sold);
return;
}else{
status = state.failure;
emit Failure(computed_fundraise, final_share_price);
return;
}
}
}
}
/// @notice Helper function that calculates the valuation of the asset
/// in terms of an ART token quantity.
function convert_valuation_to_art(uint _valuation, uint _art_price) view public returns(uint amount){
amount = ((
_valuation.mul(oracle_price_decimals_factor)
).div(
_art_price
)).mul(decimal_precission_difference_factor);
}
/// @notice Performs the refund of the ignored bids ART tokens
/// @dev Since this function is resource intensive, computation is done in batches
/// of `_count` bids, so as to not encounter an OutOfGas exception in the middle
/// of the process.
/// @param _count Amount of bids to be processed in this run.
function refundIgnoredBids(uint _count) public onlyOwner{
require(status == state.success || status == state.failure);
uint count = _count;
if(bids_ignored_count < bids_ignored_refunded.add(count)){
count = bids_ignored_count.sub(bids_ignored_refunded);
}
require(count > 0);
uint cursor = bids_ignored_refunded;
bids_ignored_refunded = bids_ignored_refunded.add(count);
BidData storage bid;
while (count > 0) {
bid = bids_ignored[cursor];
if(bid.closed){
continue;
}
bid.closed = true;
art_token_contract.transfer(bid.investor_address, bid.transfer_token);
emit Refunded(bid.origin_index, bid.transfer_token);
cursor ++;
count --;
}
}
/// @notice Performs the refund of the "loser" bids ART tokens
/// @dev Since this function is resource intensive, computation is done in batches
/// of `_count` bids, so as to not encounter an OutOfGas exception in the middle
/// of the process.
/// @param _count Amount of bids to be processed in this run.
function refundLosersBids(uint _count) public onlyOwner{
require(status == state.success || status == state.failure);
uint count = _count;
if(bids_sorted_count.sub(winner_bids) < bids_sorted_refunded.add(count)){
count = bids_sorted_count.sub(winner_bids).sub(bids_sorted_refunded);
}
require(count > 0);
uint cursor = bids_sorted_refunded.add(winner_bids);
bids_sorted_refunded = bids_sorted_refunded.add(count);
BidData memory bid;
while (count > 0) {
bid = bids_sorted[cursor];
if(bid.closed){
continue;
}
bids_sorted[cursor].closed = true;
art_token_contract.transfer(bid.investor_address, bid.transfer_token);
emit Refunded(bid.origin_index, bid.transfer_token);
cursor ++;
count --;
}
}
/// @notice Calculates how many shares are assigned to a bid.
/// @param _shares_count Amount of shares bid for.
/// @param _transfer_valuation Unused parameter
/// @param _final_share_price Final share price calculated from all winning bids
/// @param _art_price Price of the ART token
/// @param transfer_token Amount of ART tokens transferred with the bid
function calculate_shares_and_return(uint _shares_count, uint _share_price, uint _transfer_valuation, uint _final_share_price, uint _art_price, uint transfer_token) view public
returns(
uint _shares_to_assign,
uint _executed_amount_valuation,
uint _return_amount
){
if(assigned_shares.add(_shares_count) > max_shares_to_sell){
_shares_to_assign = max_shares_to_sell.sub(assigned_shares);
}else{
_shares_to_assign = _shares_count;
}
_executed_amount_valuation = _shares_to_assign.mul(_final_share_price);
if (final_fundraise.add(_executed_amount_valuation) > fundraise_max){
_executed_amount_valuation = fundraise_max.sub(final_fundraise);
_shares_to_assign = _executed_amount_valuation.div(_final_share_price);
_executed_amount_valuation = _shares_to_assign.mul(_final_share_price);
}
uint _executed_amount = convert_valuation_to_art(_executed_amount_valuation, _art_price);
_return_amount = transfer_token.sub(_executed_amount);
}
/// @notice Assign the asset share tokens to winner bid's authors
/// @dev Since this function is resource intensive, computation is done in batches
/// of `_count` bids, so as to not encounter an OutOfGas exception in the middle
/// of the process.
/// @param _count Amount of bids to be processed in this run.
function assignShareTokens(uint _count) public onlyOwner{
require(status == state.success);
uint count = _count;
if(winner_bids < assigned_bids.add(count)){
count = winner_bids.sub(assigned_bids);
}
require(count > 0);
uint cursor = assigned_bids;
assigned_bids = assigned_bids.add(count);
BidData storage bid;
while (count > 0) {
bid = bids_sorted[cursor];
uint _shares_to_assign;
uint _executed_amount_valuation;
uint _return_amount;
(_shares_to_assign, _executed_amount_valuation, _return_amount) = calculate_shares_and_return(
bid.shares_count,
bid.share_price,
bid.transfer_valuation,
final_share_price,
bids[bid.origin_index].art_price,
bid.transfer_token
);
bid.executed_amount = _executed_amount_valuation;
bid.asigned_shares_count = _shares_to_assign;
assigned_shares = assigned_shares.add(_shares_to_assign);
final_fundraise = final_fundraise.add(_executed_amount_valuation);
final_shares_sold = final_shares_sold.add(_shares_to_assign);
if(_return_amount > 0){
art_token_contract.transfer(bid.investor_address, _return_amount);
}
bid.closed = true;
if (shares_holders_balance[bid.investor_address] == 0){
shares_holders[shares_holders_count++] = bid.investor_address;
}
emit Assigned(bid.origin_index,_shares_to_assign, _executed_amount_valuation, _return_amount);
shares_holders_balance[bid.investor_address] = shares_holders_balance[bid.investor_address].add(_shares_to_assign);
cursor ++;
count --;
}
}
/**
* @dev Return share balance of sender
* @return uint256 share_balance
*/
function getShareBalance() view public returns (uint256 share_balance){
require(status == state.success);
require(winner_bids == assigned_bids);
share_balance = shares_holders_balance[msg.sender];
}
/**
* @dev Reclaim all (Except ART) ERC20Basic compatible tokens
* @param token ERC20Basic The address of the token contract
*/
function reclaimToken(ERC20Basic token) external onlyOwner {
require(token != art_token_contract);
uint256 balance = token.balanceOf(this);
token.transfer(owner, balance);
}
function reclaim_art_token() external onlyOwner {
require(status == state.success || status == state.failure);
require(winner_bids == assigned_bids);
uint256 balance = art_token_contract.balanceOf(this);
art_token_contract.transfer(owner, balance);
}
/// @notice Proxy function which allows sending of transactions
/// in behalf of the contract
function executeTransaction(
address destination,
uint value,
bytes data
)
public
onlyOwner
{
if (destination.call.value(value)(data))
emit Execution(destination,value,data);
else
emit ExecutionFailure(destination,value,data);
}
}
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
// Gas optimization: this is cheaper than asserting 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
// uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return a / b;
}
/**
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
}
pragma solidity ^0.4.13;
contract OracleInterface {
struct PriceData {
uint ARTTokenPrice;
uint blockHeight;
}
mapping(uint => PriceData) public historicPricing;
uint public index;
address public owner;
uint8 public decimals;
function setPrice(uint price) public returns (uint _index) {}
function getPrice() public view returns (uint price, uint _index, uint blockHeight) {}
function getHistoricalPrice(uint _index) public view returns (uint price, uint blockHeight) {}
event Updated(uint indexed price, uint indexed index);
}
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
contract ERC20Interface is ERC20Basic {
uint8 public decimals;
}
contract HasNoTokens {
/**
* @dev Reject all ERC223 compatible tokens
* @param from_ address The address that is transferring the tokens
* @param value_ uint256 the amount of the specified token
* @param data_ Bytes The data passed from the caller.
*/
function tokenFallback(address from_, uint256 value_, bytes data_) external {
from_;
value_;
data_;
revert();
}
}
contract Ownable {
address public owner;
event OwnershipRenounced(address indexed previousOwner);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipRenounced(owner);
owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function transferOwnership(address _newOwner) public onlyOwner {
_transferOwnership(_newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function _transferOwnership(address _newOwner) internal {
require(_newOwner != address(0));
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
}
}
contract HasNoEther is Ownable {
/**
* @dev Constructor that rejects incoming Ether
* @dev The `payable` flag is added so we can access `msg.value` without compiler warning. If we
* leave out payable, then Solidity will allow inheriting contracts to implement a payable
* constructor. By doing it this way we prevent a payable constructor from working. Alternatively
* we could use assembly to access msg.value.
*/
constructor() public payable {
require(msg.value == 0);
}
/**
* @dev Disallows direct send by settings a default function without the `payable` flag.
*/
function() external {
}
/**
* @dev Transfer all Ether held by the contract to the owner.
*/
function reclaimEther() external onlyOwner {
owner.transfer(address(this).balance);
}
}
contract DutchAuction is Ownable, HasNoEther, HasNoTokens {
using SafeMath for uint256;
/// @notice Auction Data
uint public min_shares_to_sell;
uint public max_shares_to_sell;
uint public min_share_price;
uint public available_shares;
bool private fundraise_defined;
uint public fundraise_max;
/// @notice Auction status
state public status = state.pending;
enum state { pending, active, ended, decrypted, success, failure }
/// @notice Events
event Started(uint block_number);
event BidAdded(uint index);
event Ended(uint block_number);
event BidDecrypted(uint index, bool it_will_process);
event FundraiseDefined(uint min_share_price, uint max);
event BidBurned(uint index);
event Decrypted(uint blocknumber, uint bids_decrypted, uint bids_burned);
event Computed(uint index, uint share_price, uint shares_count);
event Assigned(uint index, uint shares, uint executed_amout, uint refunded);
event Refunded(uint index, uint refunded);
event Success(uint raised, uint share_price, uint delivered_shares);
event Failure(uint raised, uint share_price);
event Execution(address destination,uint value,bytes data);
event ExecutionFailure(address destination,uint value,bytes data);
/// @notice Token assignment data
uint public final_share_price;
uint public computed_fundraise;
uint public final_fundraise;
uint public computed_shares_sold;
uint public final_shares_sold;
uint public winner_bids;
uint public assigned_bids;
uint public assigned_shares;
/// @notice Bidding data
struct BidData {
uint origin_index;
uint bid_id;
address investor_address;
uint share_price;
uint shares_count;
uint transfer_valuation;
uint transfer_token;
uint asigned_shares_count;
uint executed_amount;
bool closed;
}
uint public bids_sorted_count;
uint public bids_sorted_refunded;
mapping (uint => BidData) public bids_sorted; //Is sorted
uint public bids_burned_count;
mapping (uint => uint) public bids_burned;
uint public bids_ignored_count;
uint public bids_ignored_refunded;
mapping (uint => BidData) public bids_ignored;
uint public bids_decrypted_count;
mapping (uint => uint) public bids_decrypted;
uint private bids_reset_count;
struct Bid {
// https://ethereum.stackexchange.com/questions/3184/what-is-the-cheapest-hash-function-available-in-solidity#3200
bytes32 bid_hash;
uint art_price;
uint art_price_index;
bool exist;
bool is_decrypted;
bool is_burned;
bool will_compute;
}
uint public bids_count;
mapping (uint => Bid) public bids;
uint public bids_computed_cursor;
uint public shares_holders_count;
mapping (uint => address) public shares_holders;
mapping (address => uint) public shares_holders_balance;
/// @notice External dependencies
OracleInterface oracle;
uint public oracle_price_decimals_factor;
ERC20Interface art_token_contract;
uint public decimal_precission_difference_factor;
/// @notice Set up the dutch auction
/// @param _min_shares_to_sell The minimum amount of asset shares to be sold
/// @param _max_shares_to_sell The maximum amount of asset shares to be sold
/// @param _available_shares The total share amount the asset will be divided into
/// @param _oracle Address of the ART/USD price oracle contract
/// @param _art_token_contract Address of the ART token contract
constructor(
uint _min_shares_to_sell,
uint _max_shares_to_sell,
uint _available_shares,
address _oracle,
address _art_token_contract
) public {
require(_max_shares_to_sell > 0);
require(_max_shares_to_sell >= _min_shares_to_sell);
require(_available_shares >= _max_shares_to_sell);
require(_oracle != address(0x0));
owner = msg.sender;
min_shares_to_sell = _min_shares_to_sell;
max_shares_to_sell = _max_shares_to_sell;
available_shares = _available_shares;
oracle = OracleInterface(_oracle);
uint256 oracle_decimals = uint256(oracle.decimals());
oracle_price_decimals_factor = 10**oracle_decimals;
art_token_contract = ERC20Interface(_art_token_contract);
uint256 art_token_decimals = uint256(art_token_contract.decimals());
decimal_precission_difference_factor = 10**(art_token_decimals.sub(oracle_decimals));
}
/// @notice Allows configuration of the final parameters needed for
/// auction end state calculation. This is only allowed once the auction
/// has closed and no more bids can enter
/// @param _min_share_price Minimum price accepted for individual asset shares
/// @param _fundraise_max Maximum cap for fundraised capital
function setFundraiseLimits(uint _min_share_price, uint _fundraise_max) public onlyOwner{
require(!fundraise_defined);
require(_min_share_price > 0);
require(_fundraise_max > 0);
require(status == state.ended);
fundraise_max = _fundraise_max;
min_share_price = _min_share_price;
emit FundraiseDefined(min_share_price,fundraise_max);
fundraise_defined = true;
}
/// @notice Starts the auction
function startAuction() public onlyOwner{
require(status == state.pending);
status = state.active;
emit Started(block.number);
}
/// @notice Ends the auction, preventing new bids from entering
function endAuction() public onlyOwner{
require(status == state.active);
status = state.ended;
emit Ended(block.number);
}
/// @notice Append an encrypted bid to the auction. This allows the contract
/// to keep a count on how many bids it has, while staying ignorant of the
/// bid contents.
function appendEncryptedBid(bytes32 _bid_hash, uint price_index) public onlyOwner returns (uint index){
require(status == state.active);
uint art_price;
uint art_price_blockHeight;
(art_price, art_price_blockHeight) = oracle.getHistoricalPrice(price_index);
bids[bids_count] = Bid(_bid_hash, art_price, price_index, true, false, false, false);
index = bids_count;
emit BidAdded(bids_count++);
}
/// @notice Helper function for calculating a bid's hash.
function getBidHash(uint nonce, uint bid_id, address investor_address, uint share_price, uint shares_count) public pure returns(bytes32) {
return keccak256(abi.encodePacked(nonce, bid_id, investor_address, share_price, shares_count));
}
/// @notice Allows the "burning" of a bid, for cases in which a bid was corrupted and can't be decrypted.
/// "Burnt" bids do not participate in the final calculations for auction participants
/// @param _index Indicates the index of the bid to be burnt
function burnBid(uint _index) public onlyOwner {
require(status == state.ended);
require(bids_sorted_count == 0);
require(bids[_index].exist == true);
require(bids[_index].is_decrypted == false);
require(bids[_index].is_burned == false);
bids_burned[bids_burned_count] = _index;
bids_burned_count++;
bids_decrypted[bids_decrypted_count] = _index;
bids_decrypted_count++;
bids[_index].is_burned = true;
emit BidBurned(_index);
}
/// @notice Appends the bid's data to the contract, for use in the final calculations
/// Once all bids are appended, the auction is locked and changes its state to "decrypted"
/// @dev Bids MUST be appended in order of asset valuation,
/// since the contract relies on off-chain sorting and checks if the order is correct
/// @param _nonce Bid parameter
/// @param _index Bid's index inside the contract
/// @param _bid_id Bid parameter
/// @param _investor_address Bid parameter - address of the bid's originator
/// @param _share_price Bid parameter - estimated value of the asset's share price
/// @param _shares_count Bid parameter - amount of shares bid for
/// @param _transfered_token Bid parameter - amount of ART tokens sent with the bid
function appendDecryptedBid(uint _nonce, uint _index, uint _bid_id, address _investor_address, uint _share_price, uint _shares_count, uint _transfered_token) onlyOwner public {
require(status == state.ended);
require(fundraise_defined);
require(bids[_index].exist == true);
require(bids[_index].is_decrypted == false);
require(bids[_index].is_burned == false);
require(_share_price > 0);
require(_shares_count > 0);
require(_transfered_token >= convert_valuation_to_art(_shares_count.mul(_share_price),bids[_index].art_price));
if (bids_sorted_count > 0){
BidData memory previous_bid_data = bids_sorted[bids_sorted_count-1];
require(_share_price <= previous_bid_data.share_price);
if (_share_price == previous_bid_data.share_price){
require(_index > previous_bid_data.origin_index);
}
}
require(
getBidHash(_nonce, _bid_id,_investor_address,_share_price,_shares_count) == bids[_index].bid_hash
);
uint _transfer_amount = _share_price.mul(_shares_count);
BidData memory bid_data = BidData(_index, _bid_id, _investor_address, _share_price, _shares_count, _transfer_amount, _transfered_token, 0, 0, false);
bids[_index].is_decrypted = true;
if (_share_price >= min_share_price){
bids[_index].will_compute = true;
bids_sorted[bids_sorted_count] = bid_data;
bids_sorted_count++;
emit BidDecrypted(_index,true);
}else{
bids[_index].will_compute = false;
bids_ignored[bids_ignored_count] = bid_data;
bids_ignored_count++;
emit BidDecrypted(_index,false);
}
bids_decrypted[bids_decrypted_count] = _index;
bids_decrypted_count++;
if(bids_decrypted_count == bids_count){
emit Decrypted(block.number, bids_decrypted_count.sub(bids_burned_count), bids_burned_count);
status = state.decrypted;
}
}
/// @notice Allows appending multiple decrypted bids (in order) at once.
/// @dev Parameters are the same as appendDecryptedBid but in array format.
function appendDecryptedBids(uint[] _nonce, uint[] _index, uint[] _bid_id, address[] _investor_address, uint[] _share_price, uint[] _shares_count, uint[] _transfered_token) public onlyOwner {
require(_nonce.length == _index.length);
require(_index.length == _bid_id.length);
require(_bid_id.length == _investor_address.length);
require(_investor_address.length == _share_price.length);
require(_share_price.length == _shares_count.length);
require(_shares_count.length == _transfered_token.length);
require(bids_count.sub(bids_decrypted_count) > 0);
for (uint i = 0; i < _index.length; i++){
appendDecryptedBid(_nonce[i], _index[i], _bid_id[i], _investor_address[i], _share_price[i], _shares_count[i], _transfered_token[i]);
}
}
/// @notice Allows resetting the entire bid decryption/appending process
/// in case a mistake was made and it is not possible to continue appending further bids.
function resetAppendDecryptedBids(uint _count) public onlyOwner{
require(status == state.ended);
require(bids_decrypted_count > 0);
require(_count > 0);
if (bids_reset_count == 0){
bids_reset_count = bids_decrypted_count;
}
uint count = _count;
if(bids_reset_count < count){
count = bids_reset_count;
}
do {
bids_reset_count--;
bids[bids_decrypted[bids_reset_count]].is_decrypted = false;
bids[bids_decrypted[bids_reset_count]].is_burned = false;
bids[bids_decrypted[bids_reset_count]].will_compute = false;
count--;
} while(count > 0);
if (bids_reset_count == 0){
bids_sorted_count = 0;
bids_ignored_count = 0;
bids_decrypted_count = 0;
bids_burned_count = 0;
}
}
/// @notice Performs the computation of auction winners and losers.
/// Also, determines if the auction is successful or failed.
/// Bids which place the asset valuation below the minimum fundraise cap
/// as well as bids below the final valuation are marked as ignored or "loser" respectively
/// and do not count towards the process.
/// @dev Since this function is resource intensive, computation is done in batches
/// of `_count` bids, so as to not encounter an OutOfGas exception in the middle
/// of the process.
/// @param _count Amount of bids to be processed in this run.
function computeBids(uint _count) public onlyOwner{
require(status == state.decrypted);
require(_count > 0);
uint count = _count;
// No bids
if (bids_sorted_count == 0){
status = state.failure;
emit Failure(0, 0);
return;
}
//bids_computed_cursor: How many bid already processed
//bids_sorted_count: How many bids can compunte
require(bids_computed_cursor < bids_sorted_count);
//bid: Auxiliary variable
BidData memory bid;
do{
//bid: Current bid to compute
bid = bids_sorted[bids_computed_cursor];
//if only one share of current bid leave us out of fundraise limitis, ignore the bid
//computed_shares_sold: Sumarize shares sold
if (bid.share_price.mul(computed_shares_sold).add(bid.share_price) > fundraise_max){
if(bids_computed_cursor > 0){
bids_computed_cursor--;
}
bid = bids_sorted[bids_computed_cursor];
break;
}
//computed_shares_sold: Sumarize cumpued shares
computed_shares_sold = computed_shares_sold.add(bid.shares_count);
//computed_fundraise: Sumarize fundraise
computed_fundraise = bid.share_price.mul(computed_shares_sold);
emit Computed(bid.origin_index, bid.share_price, bid.shares_count);
//Next bid
bids_computed_cursor++;
count--;
}while(
count > 0 && //We have limite to compute
bids_computed_cursor < bids_sorted_count && //We have more bids to compute
(
computed_fundraise < fundraise_max && //Fundraise is more or equal to max
computed_shares_sold < max_shares_to_sell //Assigned shares are more or equal to max
)
);
if (
bids_computed_cursor == bids_sorted_count || //All bids computed
computed_fundraise >= fundraise_max ||//Fundraise is more or equal to max
computed_shares_sold >= max_shares_to_sell//Max shares raised
){
final_share_price = bid.share_price;
//More than max shares
if(computed_shares_sold >= max_shares_to_sell){
computed_shares_sold = max_shares_to_sell;//Limit shares
computed_fundraise = final_share_price.mul(computed_shares_sold);
winner_bids = bids_computed_cursor;
status = state.success;
emit Success(computed_fundraise, final_share_price, computed_shares_sold);
return;
}
//Max fundraise is raised
if(computed_fundraise.add(final_share_price.mul(1)) >= fundraise_max){//More than max fundraise
computed_fundraise = fundraise_max;//Limit fundraise
winner_bids = bids_computed_cursor;
status = state.success;
emit Success(computed_fundraise, final_share_price, computed_shares_sold);
return;
}
//All bids computed
if (bids_computed_cursor == bids_sorted_count){
if (computed_shares_sold >= min_shares_to_sell){
winner_bids = bids_computed_cursor;
status = state.success;
emit Success(computed_fundraise, final_share_price, computed_shares_sold);
return;
}else{
status = state.failure;
emit Failure(computed_fundraise, final_share_price);
return;
}
}
}
}
/// @notice Helper function that calculates the valuation of the asset
/// in terms of an ART token quantity.
function convert_valuation_to_art(uint _valuation, uint _art_price) view public returns(uint amount){
amount = ((
_valuation.mul(oracle_price_decimals_factor)
).div(
_art_price
)).mul(decimal_precission_difference_factor);
}
/// @notice Performs the refund of the ignored bids ART tokens
/// @dev Since this function is resource intensive, computation is done in batches
/// of `_count` bids, so as to not encounter an OutOfGas exception in the middle
/// of the process.
/// @param _count Amount of bids to be processed in this run.
function refundIgnoredBids(uint _count) public onlyOwner{
require(status == state.success || status == state.failure);
uint count = _count;
if(bids_ignored_count < bids_ignored_refunded.add(count)){
count = bids_ignored_count.sub(bids_ignored_refunded);
}
require(count > 0);
uint cursor = bids_ignored_refunded;
bids_ignored_refunded = bids_ignored_refunded.add(count);
BidData storage bid;
while (count > 0) {
bid = bids_ignored[cursor];
if(bid.closed){
continue;
}
bid.closed = true;
art_token_contract.transfer(bid.investor_address, bid.transfer_token);
emit Refunded(bid.origin_index, bid.transfer_token);
cursor ++;
count --;
}
}
/// @notice Performs the refund of the "loser" bids ART tokens
/// @dev Since this function is resource intensive, computation is done in batches
/// of `_count` bids, so as to not encounter an OutOfGas exception in the middle
/// of the process.
/// @param _count Amount of bids to be processed in this run.
function refundLosersBids(uint _count) public onlyOwner{
require(status == state.success || status == state.failure);
uint count = _count;
if(bids_sorted_count.sub(winner_bids) < bids_sorted_refunded.add(count)){
count = bids_sorted_count.sub(winner_bids).sub(bids_sorted_refunded);
}
require(count > 0);
uint cursor = bids_sorted_refunded.add(winner_bids);
bids_sorted_refunded = bids_sorted_refunded.add(count);
BidData memory bid;
while (count > 0) {
bid = bids_sorted[cursor];
if(bid.closed){
continue;
}
bids_sorted[cursor].closed = true;
art_token_contract.transfer(bid.investor_address, bid.transfer_token);
emit Refunded(bid.origin_index, bid.transfer_token);
cursor ++;
count --;
}
}
/// @notice Calculates how many shares are assigned to a bid.
/// @param _shares_count Amount of shares bid for.
/// @param _transfer_valuation Unused parameter
/// @param _final_share_price Final share price calculated from all winning bids
/// @param _art_price Price of the ART token
/// @param transfer_token Amount of ART tokens transferred with the bid
function calculate_shares_and_return(uint _shares_count, uint _share_price, uint _transfer_valuation, uint _final_share_price, uint _art_price, uint transfer_token) view public
returns(
uint _shares_to_assign,
uint _executed_amount_valuation,
uint _return_amount
){
if(assigned_shares.add(_shares_count) > max_shares_to_sell){
_shares_to_assign = max_shares_to_sell.sub(assigned_shares);
}else{
_shares_to_assign = _shares_count;
}
_executed_amount_valuation = _shares_to_assign.mul(_final_share_price);
if (final_fundraise.add(_executed_amount_valuation) > fundraise_max){
_executed_amount_valuation = fundraise_max.sub(final_fundraise);
_shares_to_assign = _executed_amount_valuation.div(_final_share_price);
_executed_amount_valuation = _shares_to_assign.mul(_final_share_price);
}
uint _executed_amount = convert_valuation_to_art(_executed_amount_valuation, _art_price);
_return_amount = transfer_token.sub(_executed_amount);
}
/// @notice Assign the asset share tokens to winner bid's authors
/// @dev Since this function is resource intensive, computation is done in batches
/// of `_count` bids, so as to not encounter an OutOfGas exception in the middle
/// of the process.
/// @param _count Amount of bids to be processed in this run.
function assignShareTokens(uint _count) public onlyOwner{
require(status == state.success);
uint count = _count;
if(winner_bids < assigned_bids.add(count)){
count = winner_bids.sub(assigned_bids);
}
require(count > 0);
uint cursor = assigned_bids;
assigned_bids = assigned_bids.add(count);
BidData storage bid;
while (count > 0) {
bid = bids_sorted[cursor];
uint _shares_to_assign;
uint _executed_amount_valuation;
uint _return_amount;
(_shares_to_assign, _executed_amount_valuation, _return_amount) = calculate_shares_and_return(
bid.shares_count,
bid.share_price,
bid.transfer_valuation,
final_share_price,
bids[bid.origin_index].art_price,
bid.transfer_token
);
bid.executed_amount = _executed_amount_valuation;
bid.asigned_shares_count = _shares_to_assign;
assigned_shares = assigned_shares.add(_shares_to_assign);
final_fundraise = final_fundraise.add(_executed_amount_valuation);
final_shares_sold = final_shares_sold.add(_shares_to_assign);
if(_return_amount > 0){
art_token_contract.transfer(bid.investor_address, _return_amount);
}
bid.closed = true;
if (shares_holders_balance[bid.investor_address] == 0){
shares_holders[shares_holders_count++] = bid.investor_address;
}
emit Assigned(bid.origin_index,_shares_to_assign, _executed_amount_valuation, _return_amount);
shares_holders_balance[bid.investor_address] = shares_holders_balance[bid.investor_address].add(_shares_to_assign);
cursor ++;
count --;
}
}
/**
* @dev Return share balance of sender
* @return uint256 share_balance
*/
function getShareBalance() view public returns (uint256 share_balance){
require(status == state.success);
require(winner_bids == assigned_bids);
share_balance = shares_holders_balance[msg.sender];
}
/**
* @dev Reclaim all (Except ART) ERC20Basic compatible tokens
* @param token ERC20Basic The address of the token contract
*/
function reclaimToken(ERC20Basic token) external onlyOwner {
require(token != art_token_contract);
uint256 balance = token.balanceOf(this);
token.transfer(owner, balance);
}
function reclaim_art_token() external onlyOwner {
require(status == state.success || status == state.failure);
require(winner_bids == assigned_bids);
uint256 balance = art_token_contract.balanceOf(this);
art_token_contract.transfer(owner, balance);
}
/// @notice Proxy function which allows sending of transactions
/// in behalf of the contract
function executeTransaction(
address destination,
uint value,
bytes data
)
public
onlyOwner
{
if (destination.call.value(value)(data))
emit Execution(destination,value,data);
else
emit ExecutionFailure(destination,value,data);
}
}
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
// Gas optimization: this is cheaper than asserting 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
// uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return a / b;
}
/**
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment