# Ethereum forks Counterparty in 340 lines of serpent
# Not yet tested

# assets[i] = a registered asset, assets[i].holders[j] = former or current i-holder
data assets[2^50](creator, name, calldate, callprice, dividend_paid, holders[2^50], holdersCount)
data nextAssetId

# holdersMap: holdersMap[addr][asset] = 1 if addr holds asset
data holdersMap[2^160][2^50]

# balances[x][y] = how much of y x holds
data balances[2^160][2^50]

# orders[a][b] = heap of indices to (c, d, e) 
# = c offers to sell d units of a at a price of e units of b per 10^18 units
# of a
data orderbooks[2^50][2^50]

# store of general order data
data orders[2^50](seller, asset_sold, quantity, price)
data ordersCount

# data feeds
data feeds[2^50](owner, value)
data feedCount

# heap
data heap
extern heap: [register, push, pop, top, size]

data cfds[2^50](maker, acceptor, feed, asset, strike, leverage, min, max, maturity)
data cfdCount

data bets[2^50](maker, acceptor, feed, asset, makerstake, acceptorstake, eqtest, maturity)
data betCount

def init():
    heap = create('heap.se')

# Add units (internal method)
def add(to, asset, value):
    assert msg.sender == self
    self.balances[to][asset] += value
    # Add the holder to the holders list
    if not self.holdersMap[to][asset]:
        self.holdersMap[to][asset] = 1
        c = self.assets[asset].holdersCount
        self.assets[asset].holders[c] = to
        self.assets[asset].holdersCount = c + 1

# Register a new asset
def register_asset(q, name, calldate, callprice): 
    newid = self.nextAssetId
    self.assets[newid].creator = msg.sender
    self.assets[newid].name = name
    self.assets[newid].calldate = calldate
    self.assets[newid].callprice = callprice
    self.assets[newid].holders[0] = msg.sender
    self.assets[newid].holdersCount = 1
    self.balances[msg.sender][newid] = q
    self.holdersMap[msg.sender][newid] = 1

# Send
def send(to, asset, value):
    fromval = self.balances[msg.sender][asset]
    if fromval >= value:
        self.balances[msg.sender][asset] -= value
        self.add(to, asset, value)

# Order
def mkorder(selling, buying, quantity, price):
    # Make sure you have enough to pay for the order
    assert self.balances[msg.sender][selling] >= quantity:
    # Try to match existing orders
    o = orderbooks[buying][selling]
    if not o:
        o = self.heap.register()
        orderbooks[selling][buying] = o
    sz = self.heap.size(o)
    invprice = 10^36 / price
    while quantity > 0 and sz > 0: 
        orderid = self.heap.pop()
        p = self.orders[orderid].price
        if p > invprice:
            sz = 0
        else:
            q = self.orders[orderid].quantity
            oq = min(q, quantity)
            b = self.orders[orderid].seller
            self.balances[msg.sender][selling] -= oq * p / 10^18
            self.add(msg.sender, buying, oq)
            self.add(b, selling, oq * p / 10^18)
            self.orders[orderid].quantity = q - oq
            if oq == q:
                self.orders[orderid].seller = 0
                self.orders[orderid].price = 0
                self.orders[orderid].asset_sold = 0
            quantity -= oq
            sz -= 1
    assert quantity > 0
    # Make the order
    c = self.ordersCount
    self.orders[c].seller = msg.sender
    self.orders[c].asset_sold = selling
    self.orders[c].quantity = quantity
    self.orders[c].price = price
    self.ordersCount += 1
    # Add it to the heap
    o = orderbooks[selling][buying]
    if not o:
        o = self.heap.register()
        orderbooks[selling][buying] = o
    self.balances[msg.sender][selling] -= quantity
    self.heap.push(o, price, c)
    return(c)

def cancel_order(id):
    if self.orders[id].seller == msg.sender:
        self.orders[id].seller = 0
        self.orders[id].price = 0
        self.balances[msg.sender][self.orders[id].asset_sold] += self.orders[id].quantity
        self.orders[id].quantity = 0
        self.orders[id].asset_sold = 0

def register_feed():
    c = self.feedCount
    self.feeds[c].owner = msg.sender
    self.feedCount = c + 1
    return(c)

def set_feed(id, v):
    if self.feeds[id].owner == msg.sender:
        self.feeds[id].value = v

def mk_cfd_offer(feed, asset, strike, leverage, min, max, maturity):
    b = self.balances[msg.sender][asset]
    req = max((strike - min) * leverage, (strike - max) * leverage)
    assert b >= req
    self.balances[msg.sender][asset] = b - req
    c = self.cfdCount
    self.cfds[c].maker = msg.sender
    self.cfds[c].feed = feed
    self.cfds[c].asset = asset
    self.cfds[c].strike = strike
    self.cfds[c].leverage = leverage
    self.cfds[c].min = min
    self.cfds[c].max = max
    self.cfds[c].maturity = maturity
    self.cfdCount = c + 1
    return(c)

def accept_cfd_offer(c):
    assert not self.cfds[c].acceptor and self.cfds[c].maker
    asset = self.cfds[c].asset
    strike = self.cfds[c].strike
    min = self.cfds[c].min
    max = self.cfds[c].max
    leverage = self.cfds[c].leverage
    b = self.balances[msg.sender][asset]
    req = max((min - strike) * leverage, (max - strike) * leverage)
    assert b >= req
    self.balances[msg.sender][asset] = b - req
    self.cfds[c].acceptor = msg.sender
    self.cfds[c].maturity += block.timestamp

def claim_cfd_offer(c):
    asset = self.cfds[c].asset
    strike = self.cfds[c].strike
    min = self.cfds[c].min
    max = self.cfds[c].max
    leverage = self.cfds[c].leverage
    v = self.feeds[self.cfds[c].feed].value
    assert v <= min or v >= max or block.timestamp >= self.cfds[c].maturity
    maker_req = max((strike - min) * leverage, (strike - max) * leverage)
    acceptor_req = max((min - strike) * leverage, (max - strike) * leverage)
    paydelta = (strike - v) * leverage
    self.add(self.cfds[c].maker, asset, maker_req + paydelta)
    self.add(self.cfds[c].acceptor, asset, acceptor_req - paydelta)
    self.cfds[c].maker = 0
    self.cfds[c].acceptor = 0
    self.cfds[c].feed = 0
    self.cfds[c].asset = 0
    self.cfds[c].strike = 0
    self.cfds[c].leverage = 0
    self.cfds[c].min = 0
    self.cfds[c].max = 0
    self.cfds[c].maturity = 0

def withdraw_cfd_offer(c):
    if self.cfds[c].maker == msg.sender and not self.cfds[c].acceptor:
        asset = self.cfds[c].asset
        strike = self.cfds[c].strike
        min = self.cfds[c].min
        max = self.cfds[c].max
        leverage = self.cfds[c].leverage
        maker_req = max((strike - min) * leverage, (strike - max) * leverage)
        self.balances[self.cfds[c].maker][asset] += maker_req
        self.cfds[c].maker = 0
        self.cfds[c].acceptor = 0
        self.cfds[c].feed = 0
        self.cfds[c].asset = 0
        self.cfds[c].strike = 0
        self.cfds[c].leverage = 0
        self.cfds[c].min = 0
        self.cfds[c].max = 0
        self.cfds[c].maturity = 0
        

def mk_bet_offer(feed, asset, makerstake, acceptorstake, eqtest, maturity):
    assert self.balances[msg.sender][asset] >= makerstake
    c = self.betCount
    self.bets[c].maker = msg.sender
    self.bets[c].feed = feed
    self.bets[c].asset = asset
    self.bets[c].makerstake = makerstake
    self.bets[c].acceptorstake = acceptorstake
    self.bets[c].eqtest = eqtest
    self.bets[c].maturity = maturity
    self.balances[msg.sender][asset] -= makerstake
    self.betCount = c + 1
    return(c)

def accept_bet_offer(c):
    assert self.bets[c].maker and not self.bets[c].acceptor
    asset = self.bets[c].asset
    acceptorstake = self.bets[c].acceptorstake
    assert self.balances[msg.sender][asset] >= acceptorstake
    self.balances[msg.sender][asset] -= acceptorstake
    self.bets[c].acceptor = msg.sender

def claim_bet_offer(c):
    assert block.timestamp >= self.bets[c].maturity
    v = self.feeds[self.bets[c].feed].value
    totalstake = self.bets[c].makerstake + self.bets[c].acceptorstake
    if v == self.bets[c].eqtest:
        self.add(self.bets[c].maker, self.bets[c].asset, totalstake)
    else:
        self.add(self.bets[c].acceptor, self.bets[c].asset, totalstake)
    self.bets[c].maker = 0
    self.bets[c].feed = 0
    self.bets[c].asset = 0
    self.bets[c].makerstake = 0
    self.bets[c].acceptorstake = 0
    self.bets[c].eqtest = 0
    self.bets[c].maturity = 0

def cancel_bet(c):
    assert not self.bets[c].acceptor and msg.sender == self.bets[c].maker
    self.balances[msg.sender][self.bets[c].asset] += self.bets[c].makerstake
    self.bets[c].maker = 0
    self.bets[c].feed = 0
    self.bets[c].asset = 0
    self.bets[c].makerstake = 0
    self.bets[c].acceptorstake = 0
    self.bets[c].eqtest = 0
    self.bets[c].maturity = 0

def dividend(holder_asset, divvying_asset, ratio):
    i = 0
    sz = self.assets[holder_asset].holdersCount
    t = 0
    holders = array(sz)
    payments = array(sz)
    while i < sz:
        holders[i] = self.assets[holder_asset].holders[i]
        payments[i] = self.balances[holders[i]][holder_asset] * ratio / 10^18
        t += payments[i]
        i += 1
    if self.balances[msg.sender][divvying_asset] >= t:
        i = 0
        while i < sz:
            self.add(holders[i], divvying_asset, payments[i])
            i += 1
        self.balances[msg.sender][divvying_asset] -= t
