Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
210 changes: 210 additions & 0 deletions hydrachain/examples/native/exchange/exchange_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import ethereum.utils as utils
import ethereum.slogging as slogging
import hydrachain.native_contracts as nc
from hydrachain.nc_utils import isaddress, STATUS, FORBIDDEN, OK, INSUFFICIENTFUNDS
from hydrachain.examples.native.fungible.fungible_contract import Fungible
log = slogging.get_logger('contracts.exchange')


class Partial(nc.ABIEvent):

"Triggerd when tokens are partially traded on the Exchange"
args = [dict(name='currencyPair', type='bytes32', indexed=True),
dict(name='seller', type='address', indexed=True),
dict(name='offerAmount', type='uint256', indexed=False),
dict(name='buyer', type='address', indexed=True),
dict(name='wantAmount', type='uint256', indexed=False)]


class Traded(nc.ABIEvent):

"Triggerd when tokens are traded on the Exchange"
args = [dict(name='currencyPair', type='bytes32', indexed=True),
dict(name='seller', type='address', indexed=True),
dict(name='offerAmount', type='uint256', indexed=False),
dict(name='buyer', type='address', indexed=True),
dict(name='wantAmount', type='uint256', indexed=False)]


class Exchange(nc.NativeContract):

"""
Exchange to trade token based on the Ethereum Token standard
https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs
"""

address = utils.int_to_addr(4000)
events = [Partial, Traded]

# Struct
order_creator = nc.List('address')
order_offerCurrency = nc.List('address')
order_offerAmount = nc.List('uint256')
order_wantCurrency = nc.List('address')
order_wantAmount = nc.List('uint256')
order_id = nc.Scalar('uint256')

owner = nc.Scalar('address')
nextOrderId = nc.Scalar('uint256')

def init(ctx, returns=STATUS):
log.DEV('In Exchange.init')
if isaddress(ctx.owner):
return FORBIDDEN
ctx.owner = ctx.tx_origin
# Initialize
ctx.nextOrderId = 1
return OK

@nc.constant
def get_nextOrderId(ctx, returns='uint256'):
return ctx.nextOrderId

@nc.constant
def get_orderCreator(ctx, _offerId='uint256', returns='address'):
return ctx.order_creator[_offerId - 1]

def place_order(ctx, _offerCurrency='address', _offerAmount='uint256', _wantCurrency='address', _wantAmount='uint256', returns='uint256'):
offer_id = nc.Scalar('uint256')
offer_id = 0
# Deposit, coins at address: _offerCurrency
# from: Msg Sender
# to: Exchange
# Value: _offerAmount
r = ctx.call_abi(
_offerCurrency,
Fungible.transferFrom,
ctx.msg_sender,
ctx.address,
_offerAmount)
if r is OK:
# Store Offer
offer_id = ctx.nextOrderId
ctx.nextOrderId += 1
ctx.order_creator.append(ctx.msg_sender)
ctx.order_offerCurrency.append(_offerCurrency)
ctx.order_offerAmount.append(_offerAmount)
ctx.order_wantCurrency.append(_wantCurrency)
ctx.order_wantAmount.append(_wantAmount)
return offer_id
elif r is INSUFFICIENTFUNDS:
return offer_id
else:
raise Exception()

def claim_order_partial(ctx, _offerId='uint256', _offerAmount='uint256', _wantAmount='uint256', returns=STATUS):
# Check for the right rate
if _offerAmount >= ctx.order_offerAmount[_offerId - 1] or _wantAmount >= ctx.order_wantAmount[_offerId - 1]:
return FORBIDDEN
# Assert that: offerAmount/order_offerAmount == wantAmount/order_wantAmount
if _offerAmount * ctx.order_wantAmount[_offerId - 1] != _wantAmount * ctx.order_offerAmount[_offerId - 1]:
return FORBIDDEN

# Send, coins at address: _wantCurrency
# from: Msg Sender
# to: Order Creator
# Value: _wantAmount
r = ctx.call_abi(
ctx.order_wantCurrency[_offerId - 1],
Fungible.transferFrom,
ctx.msg_sender,
ctx.order_creator[_offerId - 1],
_wantAmount)

if r is OK:
# Send, coins at address: _offerAmount
# from: Exchange
# to: Msg Sender
# Value: _offerAmount
# i.e. execute 2nd side of the trade
r1 = ctx.call_abi(
ctx.order_offerCurrency[_offerId - 1],
Fungible.transfer,
ctx.msg_sender,
_offerAmount)
# TODO CurrencyPair
# currencyPair = (self.orders[_offerId].offerCurrency / 2**32) * 2**128 + (self.orders[_offerId].wantCurrency / 2**32)
# currencyPair = ctx.order_offerCurrency[_offerId - 1] + ctx.order_wantCurrency[_offerId - 1]
currencyPair = ""
ctx.Partial(
currencyPair,
ctx.order_creator[_offerId - 1],
_offerAmount,
ctx.msg_sender,
_wantAmount)
# Update order as partially matched
ctx.order_offerAmount[_offerId - 1] -= _offerAmount
ctx.order_wantAmount[_offerId - 1] -= _wantAmount
return OK
elif r is INSUFFICIENTFUNDS:
return INSUFFICIENTFUNDS
else:
raise Exception()


def claim_order(ctx, _offerId='uint256', returns=STATUS):
# Send, coins at address: _wantCurrency
# from: Msg Sender
# to: Order Creator
# Value: _wantAmount
r = ctx.call_abi(
ctx.order_wantCurrency[_offerId - 1],
Fungible.transferFrom,
ctx.msg_sender,
ctx.order_creator[_offerId - 1],
ctx.order_wantAmount[_offerId - 1])

if r is OK:
# Send, coins at address: _offerAmount
# from: Exchange
# to: Msg Sender
# Value: _offerAmount
# i.e. execute 2nd side of the trade
r1 = ctx.call_abi(
ctx.order_offerCurrency[_offerId - 1],
Fungible.transfer,
ctx.msg_sender,
ctx.order_offerAmount[_offerId - 1])
# TODO CurrencyPair
# currencyPair = (self.orders[_offerId].offerCurrency / 2**32) * 2**128 + (self.orders[_offerId].wantCurrency / 2**32)
# currencyPair = ctx.order_offerCurrency[_offerId - 1] + ctx.order_wantCurrency[_offerId - 1]
currencyPair = ""
ctx.Traded(
currencyPair,
ctx.order_creator[_offerId - 1],
ctx.order_offerAmount[_offerId - 1],
ctx.msg_sender,
ctx.order_wantAmount[_offerId - 1])
# Update order as executed
ctx.order_creator[_offerId - 1] = '\0' * 20
ctx.order_offerCurrency[_offerId - 1] = '\0' * 20
ctx.order_offerAmount[_offerId - 1] = 0
ctx.order_wantCurrency[_offerId - 1] = '\0' * 20
ctx.order_wantAmount[_offerId - 1] = 0
return OK
elif r is INSUFFICIENTFUNDS:
return INSUFFICIENTFUNDS
else:
raise Exception()


def delete_order(ctx, _offerId='uint256', returns=STATUS):
# Only creator can delete its order
if ctx.msg_sender != ctx.order_creator[_offerId - 1]:
return FORBIDDEN
# Return, coins at address: _offerCurrency
# from: Exchange
# to: Order Creator
# Value: _offerAmount
r = ctx.call_abi(
ctx.order_offerCurrency[_offerId - 1],
Fungible.transfer,
ctx.order_creator[_offerId - 1],
ctx.order_offerAmount[_offerId - 1])
# Update order as deleted
ctx.order_creator[_offerId - 1] = '\0' * 20
ctx.order_offerCurrency[_offerId - 1] = '\0' * 20
ctx.order_offerAmount[_offerId - 1] = 0
ctx.order_wantCurrency[_offerId - 1] = '\0' * 20
ctx.order_wantAmount[_offerId - 1] = 0
return OK
187 changes: 187 additions & 0 deletions hydrachain/examples/native/exchange/test_exchange_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
from hydrachain import native_contracts as nc
from hydrachain.nc_utils import FORBIDDEN, OK, INSUFFICIENTFUNDS
from hydrachain.examples.native.fungible.fungible_contract import Fungible
from hydrachain.examples.native.exchange.exchange_contract import Exchange
from ethereum import tester
import ethereum.slogging as slogging
log = slogging.get_logger('test.exchange')


class User(object):

def __init__(self, state, address, key):
self.state = state
self.address = address
self.key = key

def add_proxy(self, name, address):
setattr(self, name, nc.tester_nac(self.state, self.key, address))


def _prepare_env(state, logs, admin, alice, bob, eur_address, usd_address, exc_address):
# Create proxy EUR
admin.add_proxy('eur', eur_address)
assert OK == admin.eur.init(2**32)
assert OK == admin.eur.transfer(alice.address, 2**31)
alice.add_proxy('eur', eur_address)
bob.add_proxy('eur', eur_address)
assert admin.eur.balanceOf(admin.address) == 2**31

# Create proxy USD
admin.add_proxy('usd', usd_address)
assert OK == admin.usd.init(2**32)
assert OK == admin.usd.transfer(bob.address, 2**31)
alice.add_proxy('usd', usd_address)
bob.add_proxy('usd', usd_address)
assert admin.usd.balanceOf(admin.address) == 2**31

# Checking Total Sum
accounts = admin.eur.get_accounts()
assert accounts == [admin.address, alice.address]
total_sum = 0
for account in accounts:
total_sum += admin.eur.balanceOf(account)
assert total_sum == 2**32
accounts = admin.usd.get_accounts()
assert accounts == [admin.address, bob.address]
total_sum = 0
for account in accounts:
total_sum += admin.usd.balanceOf(account)
assert total_sum == 2**32

# Create proxy Exchange
admin.add_proxy('exc', exc_address)
assert OK == admin.exc.init()
alice.add_proxy('exc', exc_address)
bob.add_proxy('exc', exc_address)

return True


def test_exchange_template():
"""
Tests;
Initialization,
Create orders,
Delete orders,
Partially claim orders,
Fully Claim orders.
Events;
Checking logs
"""

# Register Contract Fungible
nc.registry.register(Fungible)
nc.registry.register(Exchange)

state = tester.state()
logs = []
admin = User(state, tester.a0, tester.k0)
alice = User(state, tester.a2, tester.k2)
bob = User(state, tester.a3, tester.k3)

# create listeners
for evt_class in Fungible.events + Exchange.events:
nc.listen_logs(state, evt_class, callback=lambda e: logs.append(e))

# Initialization
eur_address = nc.tester_create_native_contract_instance(state, admin.key, Fungible)
usd_address = nc.tester_create_native_contract_instance(state, admin.key, Fungible)
exc_address = nc.tester_create_native_contract_instance(state, admin.key, Exchange)
_prepare_env(state, logs, admin, alice, bob, eur_address, usd_address, exc_address)
assert admin.eur.balanceOf(alice.address) == 2**31
alice_eur_balance = admin.eur.balanceOf(alice.address)
assert admin.usd.balanceOf(bob.address) == 2**31
bob_usd_balance = admin.usd.balanceOf(bob.address)
assert admin.exc.get_nextOrderId() == 1

# Alice does an IPO of her coin at the exchange
# Remark,
# Price is set by the division of the two quantities.
alice_coin_quantity = 100
bob_coin_quantity = 100
# Alice does a direct Debit for Exchange address
alice.eur.approve(exc_address, alice_coin_quantity)
# Place Order on Exchange
alice_offer_id = alice.exc.place_order(
eur_address,
alice_coin_quantity,
usd_address,
bob_coin_quantity)

# Check log data of Transfer Event
assert len(logs) == 4
l = logs[3]
assert l['event_type'] == 'Transfer'
assert l['from'] == alice.address
assert l['to'] == exc_address
assert l['value'] == alice_coin_quantity
assert alice_offer_id == 1
assert alice.exc.get_orderCreator(alice_offer_id) == alice.address
assert alice.exc.get_nextOrderId() == 2
# Check balances
assert admin.eur.balanceOf(alice.address) == alice_eur_balance - alice_coin_quantity
assert admin.eur.balanceOf(exc_address) == alice_coin_quantity

# Alice deletes her offer at the exchange
assert OK == alice.exc.delete_order(alice_offer_id)
# Check log data of Transfer Event
assert len(logs) == 5
l = logs[4]
assert l['event_type'] == 'Transfer'
assert l['from'] == exc_address
assert l['to'] == alice.address
assert l['value'] == alice_coin_quantity

assert alice_offer_id == 1
assert alice.exc.get_orderCreator(alice_offer_id) == '\0' * 20
assert alice.exc.get_nextOrderId() == 2
# Check balances
assert admin.eur.balanceOf(alice.address) == alice_eur_balance
assert admin.eur.balanceOf(exc_address) == 0
assert alice.eur.allowance(exc_address) == 0

# Alice tries again, willing to lower her price.
alice.eur.approve(exc_address, 100)
alice_coin_quantity = 100
bob_coin_quantity = 50
alice_offer_id = alice.exc.place_order(
eur_address,
alice_coin_quantity,
usd_address,
bob_coin_quantity)
# check logs data of Transfer Event
assert alice_offer_id == 2
assert alice.exc.get_orderCreator(alice_offer_id) == alice.address
assert alice.exc.get_nextOrderId() == 3

# Now Bob wants to share coins
# Bob does a direct Debit for Exchange address
bob.usd.approve(exc_address, bob_coin_quantity)
# First with partial amounts
alice_coin_partial = 10
bob_coin_partial = 5
assert OK == bob.exc.claim_order_partial(alice_offer_id, alice_coin_partial, bob_coin_partial)
assert admin.eur.balanceOf(alice.address) == alice_eur_balance - alice_coin_quantity
assert admin.usd.balanceOf(alice.address) == bob_coin_partial
assert admin.eur.balanceOf(bob.address) == alice_coin_partial
assert admin.usd.balanceOf(bob.address) == bob_usd_balance - bob_coin_partial
# Then the remaining amounts
assert OK == bob.exc.claim_order(alice_offer_id)
# Check balances of Exchange
assert admin.eur.balanceOf(exc_address) == 0
assert admin.usd.balanceOf(exc_address) == 0
assert alice.eur.allowance(exc_address) == 0
assert bob.usd.allowance(exc_address) == 0
# Check balances - Cross Currency
assert admin.eur.balanceOf(alice.address) == alice_eur_balance - alice_coin_quantity
assert admin.usd.balanceOf(alice.address) == bob_coin_quantity
assert admin.eur.balanceOf(bob.address) == alice_coin_quantity
assert admin.usd.balanceOf(bob.address) == bob_usd_balance - bob_coin_quantity

# Print Logs
for l in logs:
print l['event_type']

nc.registry.unregister(Fungible)
nc.registry.unregister(Exchange)