class BalanceLeaf(object): def __init__(self, balance=0): self.balance = str(balance) # Trading history self._tradingHistoryTree = SparseMerkleTree(TREE_DEPTH_TRADING_HISTORY) self._tradingHistoryTree.newTree(TradeHistoryLeaf().hash()) self._tradeHistoryLeafs = {} # print("Empty trading tree: " + str(self._tradingHistoryTree._root)) def hash(self): return mimc_hash( [int(self.balance), int(self._tradingHistoryTree._root)], 1) def fromJSON(self, jBalance): self.balance = jBalance["balance"] # Trading history tradeHistoryLeafsDict = jBalance["_tradeHistoryLeafs"] for key, val in tradeHistoryLeafsDict.items(): self._tradeHistoryLeafs[key] = TradeHistoryLeaf( val["filled"], val["cancelled"], val["orderID"]) self._tradingHistoryTree._root = jBalance["_tradingHistoryTree"][ "_root"] self._tradingHistoryTree._db.kv = jBalance["_tradingHistoryTree"][ "_db"]["kv"] def getTradeHistory(self, orderID): address = int(orderID) % (2**TREE_DEPTH_TRADING_HISTORY) # Make sure the leaf exist in our map if not (str(address) in self._tradeHistoryLeafs): return TradeHistoryLeaf() else: return self._tradeHistoryLeafs[str(address)] def updateTradeHistory(self, orderID, filled, cancelled, orderIDToStore): address = int(orderID) % (2**TREE_DEPTH_TRADING_HISTORY) # Make sure the leaf exist in our map if not (str(address) in self._tradeHistoryLeafs): self._tradeHistoryLeafs[str(address)] = TradeHistoryLeaf(0, 0, 0) leafBefore = copy.deepcopy(self._tradeHistoryLeafs[str(address)]) rootBefore = self._tradingHistoryTree._root #print("leafBefore: " + str(leafBefore)) self._tradeHistoryLeafs[str(address)].filled = str(filled) self._tradeHistoryLeafs[str(address)].cancelled = cancelled self._tradeHistoryLeafs[str(address)].orderID = int(orderIDToStore) leafAfter = copy.deepcopy(self._tradeHistoryLeafs[str(address)]) #print("leafAfter: " + str(leafAfter)) proof = self._tradingHistoryTree.createProof(address) self._tradingHistoryTree.update(address, leafAfter.hash()) rootAfter = self._tradingHistoryTree._root return TradeHistoryUpdateData(address, proof, rootBefore, rootAfter, leafBefore, leafAfter) def resetTradeHistory(self): # Trading history self._tradingHistoryTree = SparseMerkleTree(TREE_DEPTH_TRADING_HISTORY) self._tradingHistoryTree.newTree(TradeHistoryLeaf().hash()) self._tradeHistoryLeafs = {}
class BalanceLeaf(object): def __init__(self, balance=0, weightAMM=0): self.balance = str(balance) self.weightAMM = str(weightAMM) # Storage self._storageTree = SparseMerkleTree(BINARY_TREE_DEPTH_STORAGE // 2, 4) self._storageTree.newTree(StorageLeaf().hash()) self._storageLeafs = {} #print("Empty storage tree: " + str(self._storageTree._root)) def hash(self): #print("balance: " + self.balance) temp = [ int(self.balance), int(self.weightAMM), int(self._storageTree._root) ] #print(temp) return poseidon(temp, poseidonParamsBalance) def fromJSON(self, jBalance): self.balance = jBalance["balance"] self.weightAMM = jBalance["weightAMM"] # Storage storageLeafsDict = jBalance["_storageLeafs"] for key, val in storageLeafsDict.items(): self._storageLeafs[key] = StorageLeaf(val["data"], val["storageID"]) self._storageTree._root = jBalance["_storageTree"]["_root"] self._storageTree._db.kv = jBalance["_storageTree"]["_db"]["kv"] def getStorage(self, storageID): address = int(storageID) % (2**BINARY_TREE_DEPTH_STORAGE) # Make sure the leaf exist in our map if not (str(address) in self._storageLeafs): return StorageLeaf() else: return self._storageLeafs[str(address)] def updateStorage(self, storageID, data): address = int(storageID) % (2**BINARY_TREE_DEPTH_STORAGE) # Make sure the leaf exist in our map if not (str(address) in self._storageLeafs): self._storageLeafs[str(address)] = StorageLeaf(0, 0) leafBefore = copy.deepcopy(self._storageLeafs[str(address)]) rootBefore = self._storageTree._root #print("leafBefore: " + str(leafBefore)) self._storageLeafs[str(address)].data = str(data) self._storageLeafs[str(address)].storageID = str(storageID) leafAfter = copy.deepcopy(self._storageLeafs[str(address)]) #print("leafAfter: " + str(leafAfter)) proof = self._storageTree.createProof(address) self._storageTree.update(address, leafAfter.hash()) rootAfter = self._storageTree._root return StorageUpdateData(storageID, proof, rootBefore, rootAfter, leafBefore, leafAfter)
class State(object): def __init__(self, exchangeID): self.exchangeID = int(exchangeID) # Accounts self._accountsTree = SparseMerkleTree(TREE_DEPTH_ACCOUNTS // 2, 4) self._accountsTree.newTree(getDefaultAccount().hash()) self._accounts = {} self._accounts[str(0)] = getDefaultAccount() # print("Empty accounts tree: " + str(hex(self._accountsTree._root))) def load(self, filename): with open(filename) as f: data = json.load(f) self.exchangeID = int(data["exchangeID"]) # Accounts accountLeafsDict = data["accounts_values"] for key, val in accountLeafsDict.items(): account = getDefaultAccount() account.fromJSON(val) self._accounts[key] = account self._accountsTree._root = data["accounts_root"] self._accountsTree._db.kv = data["accounts_tree"] def save(self, filename): with open(filename, "w") as file: file.write( json.dumps( { "exchangeID": self.exchangeID, "accounts_values": self._accounts, "accounts_root": self._accountsTree._root, "accounts_tree": self._accountsTree._db.kv, }, default=lambda o: o.__dict__, sort_keys=True, indent=4)) def calculateFees(self, amountB, feeBips, protocolFeeBips, rebateBips): protocolFee = (amountB * protocolFeeBips) // 100000 fee = (amountB * feeBips) // 10000 rebate = (amountB * rebateBips) // 10000 return (fee, protocolFee, rebate) def getMaxFillAmounts(self, order): account = self.getAccount(order.accountID) tradeHistory = account.getBalanceLeaf(order.tokenS).getTradeHistory( int(order.orderID)) # Trade history trimming bNew = tradeHistory.orderID < order.orderID bTrim = not (tradeHistory.orderID <= order.orderID) filled = 0 if bNew else int(tradeHistory.filled) cancelledToStore = 0 if bNew else int(tradeHistory.cancelled) cancelled = 1 if bTrim else cancelledToStore orderIDToStore = int(order.orderID) if bNew else tradeHistory.orderID """ print("bNew: " + str(bNew)) print("bTrim: " + str(bTrim)) print("filled: " + str(filled)) print("cancelledToStore: " + str(cancelledToStore)) print("cancelled: " + str(cancelled)) print("orderIDToStore: " + str(orderIDToStore)) """ # Scale the order balanceS = int(account.getBalance(order.tokenS)) limit = int(order.amountB) if order.buy else int(order.amountS) filledLimited = limit if limit < filled else filled remainingBeforeCancelled = limit - filledLimited remaining = 0 if cancelled else remainingBeforeCancelled remainingS_buy = remaining * int(order.amountS) // int(order.amountB) remainingS = remainingS_buy if order.buy else remaining fillAmountS = balanceS if balanceS < remainingS else remainingS fillAmountB = fillAmountS * int(order.amountB) // int(order.amountS) return (Fill(fillAmountS, fillAmountB), filled, cancelledToStore, orderIDToStore) def match(self, takerOrder, takerFill, makerOrder, makerFill): if takerFill.B < makerFill.S: makerFill.S = takerFill.B makerFill.B = takerFill.B * int(makerOrder.amountB) // int( makerOrder.amountS) else: takerFill.S = makerFill.S * int(takerOrder.amountS) // int( takerOrder.amountB) takerFill.B = makerFill.S spread = takerFill.S - makerFill.B matchable = makerFill.B <= takerFill.S return (spread, matchable) def settleRing(self, context, ring): #print("State update ring: ") (fillA, filled_A, cancelledToStore_A, orderIDToStore_A) = self.getMaxFillAmounts(ring.orderA) (fillB, filled_B, cancelledToStore_B, orderIDToStore_B) = self.getMaxFillAmounts(ring.orderB) ''' print("fillA.S: " + str(fillA.S)) print("fillA.B: " + str(fillA.B)) print("fillB.S: " + str(fillB.S)) print("fillB.B: " + str(fillB.B)) print("-------------") ''' if ring.orderA.buy: (spread, matchable) = self.match(ring.orderA, fillA, ring.orderB, fillB) fillA.S = fillB.B else: (spread, matchable) = self.match(ring.orderB, fillB, ring.orderA, fillA) fillA.B = fillB.S # Check valid ring.orderA.checkValid(context, ring.orderA, fillA.S, fillA.B) ring.orderB.checkValid(context, ring.orderB, fillB.S, fillB.B) ring.valid = matchable and ring.orderA.valid and ring.orderB.valid #print("ring.orderA.valid " + str(ring.orderA.valid)) #print("ring.orderB.valid " + str(ring.orderB.valid)) if ring.valid == False: #print("ring.valid false: ") fillA.S = 0 fillA.B = 0 fillB.S = 0 fillB.B = 0 # Saved in ring for tests ring.fFillS_A = toFloat(fillA.S, Float24Encoding) ring.fFillS_B = toFloat(fillB.S, Float24Encoding) fillA.S = roundToFloatValue(fillA.S, Float24Encoding) fillB.S = roundToFloatValue(fillB.S, Float24Encoding) fillA.B = fillB.S fillB.B = fillA.S ''' print("fillA.S: " + str(fillA.S)) print("fillA.B: " + str(fillA.B)) print("fillB.S: " + str(fillB.S)) print("fillB.B: " + str(fillB.B)) print("spread: " + str(spread)) ''' # Copy the initial merkle root accountsMerkleRoot = self._accountsTree._root (fee_A, protocolFee_A, rebate_A) = self.calculateFees(fillA.B, ring.orderA.feeBips, context.protocolTakerFeeBips, ring.orderA.rebateBips) (fee_B, protocolFee_B, rebate_B) = self.calculateFees(fillB.B, ring.orderB.feeBips, context.protocolMakerFeeBips, ring.orderB.rebateBips) ''' print("fee_A: " + str(fee_A)) print("protocolFee_A: " + str(protocolFee_A)) print("rebate_A: " + str(rebate_A)) print("fee_B: " + str(fee_B)) print("protocolFee_B: " + str(protocolFee_B)) print("rebate_B: " + str(rebate_B)) ''' # Update balances A accountA = self.getAccount(ring.orderA.accountID) rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(ring.orderA.accountID)) proof = self._accountsTree.createProof(ring.orderA.accountID) (balanceUpdateS_A, tradeHistoryUpdate_A) = accountA.updateBalanceAndTradeHistory( ring.orderA.tokenS, ring.orderA.orderID, -fillA.S, filled_A + (fillA.B if ring.orderA.buy else fillA.S), cancelledToStore_A, orderIDToStore_A) balanceUpdateB_A = accountA.updateBalance(ring.orderA.tokenB, fillA.B - fee_A + rebate_A) self.updateAccountTree(ring.orderA.accountID) accountAfter = copyAccountInfo(self.getAccount(ring.orderA.accountID)) rootAfter = self._accountsTree._root accountUpdate_A = AccountUpdateData(ring.orderA.accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Update balances B accountB = self.getAccount(ring.orderB.accountID) rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(ring.orderB.accountID)) proof = self._accountsTree.createProof(ring.orderB.accountID) (balanceUpdateS_B, tradeHistoryUpdate_B) = accountB.updateBalanceAndTradeHistory( ring.orderB.tokenS, ring.orderB.orderID, -fillB.S, filled_B + (fillB.B if ring.orderB.buy else fillB.S), cancelledToStore_B, orderIDToStore_B) balanceUpdateB_B = accountB.updateBalance(ring.orderB.tokenB, fillB.B - fee_B + rebate_B) self.updateAccountTree(ring.orderB.accountID) accountAfter = copyAccountInfo(self.getAccount(ring.orderB.accountID)) rootAfter = self._accountsTree._root accountUpdate_B = AccountUpdateData(ring.orderB.accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Protocol fee payment balanceUpdateA_P = self.getAccount(0).updateBalance( ring.orderA.tokenB, protocolFee_A) balanceUpdateB_P = self.getAccount(0).updateBalance( ring.orderB.tokenB, protocolFee_B) ### # Operator payment balanceDeltaA_O = fee_A - protocolFee_A - rebate_A balanceDeltaB_O = fee_B - protocolFee_B - rebate_B # The Merkle tree update is done after all rings are settled return RingSettlement(ring, accountsMerkleRoot, tradeHistoryUpdate_A, tradeHistoryUpdate_B, balanceUpdateS_A, balanceUpdateB_A, accountUpdate_A, balanceUpdateS_B, balanceUpdateB_B, accountUpdate_B, balanceUpdateA_P, balanceUpdateB_P, balanceDeltaA_O, balanceDeltaB_O) def deposit(self, accountID, secretKey, publicKeyX, publicKeyY, token, amount): # Copy the initial merkle root rootBefore = self._accountsTree._root if not (str(accountID) in self._accounts): accountBefore = copyAccountInfo(getDefaultAccount()) else: accountBefore = copyAccountInfo(self.getAccount(accountID)) proof = self._accountsTree.createProof(accountID) # Create the account if necessary if not (str(accountID) in self._accounts): self._accounts[str(accountID)] = Account( secretKey, Point(publicKeyX, publicKeyY)) account = self.getAccount(accountID) balanceUpdate = account.updateBalance(token, amount) # Update keys account.secretKey = str(secretKey) account.publicKeyX = str(publicKeyX) account.publicKeyY = str(publicKeyY) self._accountsTree.update(accountID, account.hash()) accountAfter = copyAccountInfo(account) rootAfter = self._accountsTree._root accountUpdate = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) return Deposit(amount, balanceUpdate, accountUpdate) def getAccount(self, accountID): # Make sure the leaf exist in our map if not (str(accountID) in self._accounts): print("Account doesn't exist: " + str(accountID)) return self._accounts[str(accountID)] def onchainWithdraw(self, exchangeID, accountID, tokenID, amountRequested, shutdown): # When a withdrawal is done before the deposit (account creation) we shouldn't # do anything. Just leave everything as it is. if str(accountID) in self._accounts: # Calculate amount withdrawn balance = int(self.getAccount(accountID).getBalance(tokenID)) uAmountMin = int(amountRequested) if ( int(amountRequested) < balance) else balance # Withdraw the complete balance in shutdown uAmount = balance if shutdown else uAmountMin fAmount = toFloat(uAmount, Float28Encoding) amount = fromFloat(fAmount, Float28Encoding) # Make sure no 'dust' remains after a withdrawal in shutdown amountToSubtract = uAmount if shutdown else amount # Update account rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(accountID)) proof = self._accountsTree.createProof(accountID) balanceUpdate = self.getAccount(accountID).updateBalance( tokenID, -amountToSubtract, shutdown) if shutdown: self.getAccount(accountID).publicKeyX = str(0) self.getAccount(accountID).publicKeyY = str(0) self.getAccount(accountID).nonce = 0 self.updateAccountTree(accountID) accountAfter = copyAccountInfo(self.getAccount(accountID)) rootAfter = self._accountsTree._root accountUpdate = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### else: # Dummy update fAmount = 0 rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(getDefaultAccount()) proof = self._accountsTree.createProof(accountID) balanceUpdate = getDefaultAccount().updateBalance( tokenID, 0, shutdown) accountAfter = copyAccountInfo(getDefaultAccount()) rootAfter = self._accountsTree._root accountUpdate = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### withdrawal = OnchainWithdrawal(amountRequested, balanceUpdate, accountUpdate, accountID, tokenID, fAmount) return withdrawal def offchainWithdraw(self, exchangeID, accountID, tokenID, amountRequested, operatorAccountID, feeTokenID, fee, label): feeValue = roundToFloatValue(fee, Float16Encoding) # Update account rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(accountID)) nonce = accountBefore.nonce proof = self._accountsTree.createProof(accountID) balanceUpdateF_A = self.getAccount(accountID).updateBalance( feeTokenID, -feeValue) balance = int(self.getAccount(accountID).getBalance(tokenID)) uAmountWithdrawn = int(amountRequested) if ( int(amountRequested) < balance) else balance fAmountWithdrawn = toFloat(uAmountWithdrawn, Float28Encoding) amountWithdrawn = fromFloat(fAmountWithdrawn, Float28Encoding) balanceUpdateW_A = self.getAccount(accountID).updateBalance( tokenID, -amountWithdrawn) self.getAccount(accountID).nonce += 1 self.updateAccountTree(accountID) accountAfter = copyAccountInfo(self.getAccount(accountID)) rootAfter = self._accountsTree._root accountUpdate_A = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Operator payment # This is done after all withdrawals are processed withdrawal = OffchainWithdrawal(exchangeID, accountID, tokenID, amountRequested, fAmountWithdrawn, feeTokenID, fee, label, balanceUpdateF_A, balanceUpdateW_A, accountUpdate_A, None, nonce) return withdrawal def cancelOrder(self, exchangeID, accountID, orderTokenID, orderID, operatorAccountID, feeTokenID, fee, label): feeValue = roundToFloatValue(fee, Float16Encoding) # Update account rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(accountID)) nonce = accountBefore.nonce proof = self._accountsTree.createProof(accountID) (balanceUpdateT_A, tradeHistoryUpdate_A) = self.getAccount(accountID).cancelOrder( orderTokenID, orderID) balanceUpdateF_A = self.getAccount(accountID).updateBalance( feeTokenID, -feeValue) self.getAccount(accountID).nonce += 1 self.updateAccountTree(accountID) accountAfter = copyAccountInfo(self.getAccount(accountID)) rootAfter = self._accountsTree._root accountUpdate_A = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Operator payment # This is done after all cancellations are processed cancellation = Cancellation(exchangeID, accountID, orderTokenID, orderID, feeTokenID, fee, label, nonce, tradeHistoryUpdate_A, balanceUpdateT_A, balanceUpdateF_A, accountUpdate_A, None) return cancellation def createWithdrawProof(self, exchangeID, accountID, tokenID): account = copyAccountInfo(self.getAccount(accountID)) balance = copyBalanceInfo( self.getAccount(accountID)._balancesLeafs[str(tokenID)]) accountProof = self._accountsTree.createProof(accountID) balanceProof = self.getAccount(accountID)._balancesTree.createProof( tokenID) return WithdrawProof(exchangeID, accountID, tokenID, account, balance, self.getRoot(), accountProof, balanceProof) def updateAccountTree(self, accountID): self._accountsTree.update(accountID, self.getAccount(accountID).hash()) def getRoot(self): return self._accountsTree._root
class Account(object): def __init__(self, secretKey, publicKey): self.secretKey = str(secretKey) self.publicKeyX = str(publicKey.x) self.publicKeyY = str(publicKey.y) self.nonce = 0 # Balances self._balancesTree = SparseMerkleTree(TREE_DEPTH_TOKENS // 2, 4) self._balancesTree.newTree(BalanceLeaf().hash()) self._balancesLeafs = {} #print("Empty balances tree: " + str(self._balancesTree._root)) def hash(self): return poseidon([ int(self.publicKeyX), int(self.publicKeyY), int(self.nonce), int(self._balancesTree._root) ], poseidonParamsAccount) def fromJSON(self, jAccount): self.secretKey = jAccount["secretKey"] self.publicKeyX = jAccount["publicKeyX"] self.publicKeyY = jAccount["publicKeyY"] self.nonce = int(jAccount["nonce"]) # Balances balancesLeafsDict = jAccount["_balancesLeafs"] for key, val in balancesLeafsDict.items(): balanceLeaf = BalanceLeaf() balanceLeaf.fromJSON(val) self._balancesLeafs[key] = balanceLeaf self._balancesTree._root = jAccount["_balancesTree"]["_root"] self._balancesTree._db.kv = jAccount["_balancesTree"]["_db"]["kv"] def getBalanceLeaf(self, address): # Make sure the leaf exist in our map if not (str(address) in self._balancesLeafs): return BalanceLeaf() else: return self._balancesLeafs[str(address)] def getBalance(self, address): return self.getBalanceLeaf(address).balance def updateBalance(self, tokenID, amount, shutdown=False): # Make sure the leaf exist in our map if not (str(tokenID) in self._balancesLeafs): self._balancesLeafs[str(tokenID)] = BalanceLeaf() balancesBefore = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) rootBefore = self._balancesTree._root self._balancesLeafs[str(tokenID)].balance = str( int(self._balancesLeafs[str(tokenID)].balance) + amount) if int(self._balancesLeafs[str(tokenID)].balance) > MAX_AMOUNT: self._balancesLeafs[str(tokenID)].balance = str(MAX_AMOUNT) if shutdown: self._balancesLeafs[str(tokenID)].resetTradeHistory() balancesAfter = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) proof = self._balancesTree.createProof(tokenID) self._balancesTree.update(tokenID, self._balancesLeafs[str(tokenID)].hash()) rootAfter = self._balancesTree._root return BalanceUpdateData(tokenID, proof, rootBefore, rootAfter, balancesBefore, balancesAfter) def updateBalanceAndTradeHistory(self, tokenID, orderID, amount, filled, cancelledToStore, orderIDToStore): # Make sure the leaf exist in our map if not (str(tokenID) in self._balancesLeafs): self._balancesLeafs[str(tokenID)] = BalanceLeaf() balancesBefore = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) rootBefore = self._balancesTree._root # Update filled amounts tradeHistoryUpdate = self._balancesLeafs[str( tokenID)].updateTradeHistory(orderID, filled, cancelledToStore, orderIDToStore) self._balancesLeafs[str(tokenID)].balance = str( int(self._balancesLeafs[str(tokenID)].balance) + amount) if int(self._balancesLeafs[str(tokenID)].balance) > MAX_AMOUNT: self._balancesLeafs[str(tokenID)].balance = str(MAX_AMOUNT) balancesAfter = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) proof = self._balancesTree.createProof(tokenID) self._balancesTree.update(tokenID, self._balancesLeafs[str(tokenID)].hash()) rootAfter = self._balancesTree._root return (BalanceUpdateData(tokenID, proof, rootBefore, rootAfter, balancesBefore, balancesAfter), tradeHistoryUpdate) def cancelOrder(self, tokenID, orderID): # Make sure the leaf exist in our map if not (str(tokenID) in self._balancesLeafs): self._balancesLeafs[str(tokenID)] = BalanceLeaf() balancesBefore = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) rootBefore = self._balancesTree._root # Update cancelled state tradeHistory = self._balancesLeafs[str(tokenID)].getTradeHistory( orderID) filled = int(tradeHistory.filled) if int(tradeHistory.orderID) < orderID: filled = 0 tradeHistoryUpdate = self._balancesLeafs[str( tokenID)].updateTradeHistory(orderID, filled, 1, orderID) balancesAfter = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) proof = self._balancesTree.createProof(tokenID) self._balancesTree.update(tokenID, self._balancesLeafs[str(tokenID)].hash()) rootAfter = self._balancesTree._root return (BalanceUpdateData(tokenID, proof, rootBefore, rootAfter, balancesBefore, balancesAfter), tradeHistoryUpdate)
class State(object): def __init__(self, exchangeID): self.exchangeID = int(exchangeID) # Accounts self._accountsTree = SparseMerkleTree(BINARY_TREE_DEPTH_ACCOUNTS // 2, 4) self._accountsTree.newTree(getDefaultAccount().hash()) self._accounts = {} self._accounts[str(0)] = getDefaultAccount() self._accounts[str(1)] = getDefaultAccount() # print("Empty accounts tree: " + str(hex(self._accountsTree._root))) def load(self, filename): with open(filename) as f: data = json.load(f) self.exchangeID = int(data["exchangeID"]) # Accounts accountLeafsDict = data["accounts_values"] for key, val in accountLeafsDict.items(): account = getDefaultAccount() account.fromJSON(val) self._accounts[key] = account self._accountsTree._root = data["accounts_root"] self._accountsTree._db.kv = data["accounts_tree"] def save(self, filename): with open(filename, "w") as file: file.write(json.dumps( { "exchangeID": self.exchangeID, "accounts_values": self._accounts, "accounts_root": self._accountsTree._root, "accounts_tree": self._accountsTree._db.kv, }, default=lambda o: o.__dict__, sort_keys=True, indent=4)) def calculateFees(self, amountB, feeBips, protocolFeeBips): protocolFee = (amountB * protocolFeeBips) // 100000 fee = (amountB * feeBips) // 10000 return (fee, protocolFee) def getData(self, accountID, tokenID, storageID): account = self.getAccount(accountID) storage = account.getBalanceLeaf(tokenID).getStorage(int(storageID)) # Storage trimming numSlots = (2 ** BINARY_TREE_DEPTH_STORAGE) leafStorageID = storage.storageID if int(storage.storageID) > 0 else int(storageID) % numSlots filled = int(storage.data) if (int(storageID) == int(leafStorageID)) else 0 return filled def getMaxFill(self, order, filled, balanceLimit): account = self.getAccount(order.accountID) # Scale the order balanceS = int(account.getBalance(order.tokenS)) if balanceLimit else int(order.amountS) limit = int(order.amountB) if order.fillAmountBorS else int(order.amountS) filledLimited = limit if limit < filled else filled remaining = limit - filledLimited remainingS_buy = remaining * int(order.amountS) // int(order.amountB) remainingS = remainingS_buy if order.fillAmountBorS else remaining fillAmountS = balanceS if balanceS < remainingS else remainingS fillAmountB = fillAmountS * int(order.amountB) // int(order.amountS) return Fill(fillAmountS, fillAmountB) def match(self, takerOrder, takerFill, makerOrder, makerFill): if takerFill.B < makerFill.S: makerFill.S = takerFill.B makerFill.B = takerFill.B * int(makerOrder.amountB) // int(makerOrder.amountS) else: takerFill.S = makerFill.S * int(takerOrder.amountS) // int(takerOrder.amountB) takerFill.B = makerFill.S spread = takerFill.S - makerFill.B matchable = makerFill.B <= takerFill.S return (spread, matchable) def executeTransaction(self, context, txInput): newState = GeneralObject() newState.signatureA = None newState.signatureB = None # Tokens newState.TXV_BALANCE_A_S_ADDRESS = None newState.TXV_BALANCE_A_S_ADDRESS = None # A newState.TXV_ACCOUNT_A_ADDRESS = None newState.TXV_ACCOUNT_A_OWNER = None newState.TXV_ACCOUNT_A_PUBKEY_X = None newState.TXV_ACCOUNT_A_PUBKEY_Y = None newState.TXV_ACCOUNT_A_NONCE = None newState.TXV_ACCOUNT_A_FEEBIPSAMM = None newState.TXV_BALANCE_A_S_ADDRESS = None newState.TXV_BALANCE_A_S_BALANCE = None newState.TXV_BALANCE_A_S_WEIGHT = None newState.TXV_BALANCE_A_B_BALANCE = None newState.TXV_STORAGE_A_ADDRESS = None newState.TXV_STORAGE_A_DATA = None newState.TXV_STORAGE_A_STORAGEID = None # B newState.TXV_ACCOUNT_B_ADDRESS = None newState.TXV_ACCOUNT_B_OWNER = None newState.TXV_ACCOUNT_B_PUBKEY_X = None newState.TXV_ACCOUNT_B_PUBKEY_Y = None newState.TXV_ACCOUNT_B_NONCE = None newState.TXV_BALANCE_B_S_ADDRESS = None newState.TXV_BALANCE_B_S_BALANCE = None newState.TXV_BALANCE_B_B_BALANCE = None newState.TXV_STORAGE_B_ADDRESS = None newState.TXV_STORAGE_B_DATA = None newState.TXV_STORAGE_B_STORAGEID = None # Operator newState.balanceDeltaA_O = None newState.balanceDeltaB_O = None # Protocol fees newState.balanceDeltaA_P = None newState.balanceDeltaB_P = None if txInput.txType == "Noop": # Nothing to do pass elif txInput.txType == "SpotTrade": ring = txInput # Amount filled in the trade history filled_A = self.getData(ring.orderA.accountID, ring.orderA.tokenS, ring.orderA.storageID) filled_B = self.getData(ring.orderB.accountID, ring.orderB.tokenS, ring.orderB.storageID) # Simple matching logic fillA = self.getMaxFill(ring.orderA, filled_A, True) fillB = self.getMaxFill(ring.orderB, filled_B, True) ''' print("fillA.S: " + str(fillA.S)) print("fillA.B: " + str(fillA.B)) print("fillB.S: " + str(fillB.S)) print("fillB.B: " + str(fillB.B)) print("-------------") ''' if ring.orderA.fillAmountBorS: (spread, matchable) = self.match(ring.orderA, fillA, ring.orderB, fillB) fillA.S = fillB.B else: (spread, matchable) = self.match(ring.orderB, fillB, ring.orderA, fillA) fillA.B = fillB.S # Check valid ring.orderA.checkValid(context, ring.orderA, fillA.S, fillA.B) ring.orderB.checkValid(context, ring.orderB, fillB.S, fillB.B) ring.valid = matchable and ring.orderA.valid and ring.orderB.valid #print("ring.orderA.valid " + str(ring.orderA.valid)) #print("ring.orderB.valid " + str(ring.orderB.valid)) #if ring.valid == False: #print("ring.valid false: ") #fillA.S = 0 #fillA.B = 0 #fillB.S = 0 #fillB.B = 0 # Saved in ring for tests ring.fFillS_A = toFloat(fillA.S, Float24Encoding) ring.fFillS_B = toFloat(fillB.S, Float24Encoding) fillA.S = roundToFloatValue(fillA.S, Float24Encoding) fillB.S = roundToFloatValue(fillB.S, Float24Encoding) fillA.B = fillB.S fillB.B = fillA.S ''' print("fillA.S: " + str(fillA.S)) print("fillA.B: " + str(fillA.B)) print("fillB.S: " + str(fillB.S)) print("fillB.B: " + str(fillB.B)) print("spread: " + str(spread)) ''' (fee_A, protocolFee_A) = self.calculateFees( fillA.B, ring.orderA.feeBips, context.protocolTakerFeeBips ) (fee_B, protocolFee_B) = self.calculateFees( fillB.B, ring.orderB.feeBips, context.protocolMakerFeeBips ) ''' print("fee_A: " + str(fee_A)) print("protocolFee_A: " + str(protocolFee_A)) print("fee_B: " + str(fee_B)) print("protocolFee_B: " + str(protocolFee_B)) ''' newState.signatureA = ring.orderA.signature newState.signatureB = ring.orderB.signature newState.TXV_ACCOUNT_A_ADDRESS = ring.orderA.accountID accountA = self.getAccount(ring.orderA.accountID) newState.TXV_BALANCE_A_S_ADDRESS = ring.orderA.tokenS newState.TXV_BALANCE_A_S_BALANCE = -fillA.S newState.TXV_BALANCE_B_S_ADDRESS = ring.orderA.tokenB newState.TXV_BALANCE_A_B_BALANCE = fillA.B - fee_A newState.TXV_STORAGE_A_ADDRESS = ring.orderA.storageID newState.TXV_STORAGE_A_DATA = filled_A + (fillA.B if ring.orderA.fillAmountBorS else fillA.S) newState.TXV_STORAGE_A_STORAGEID = ring.orderA.storageID newState.TXV_ACCOUNT_B_ADDRESS = ring.orderB.accountID accountB = self.getAccount(ring.orderB.accountID) newState.TXV_BALANCE_B_S_ADDRESS = ring.orderB.tokenS newState.TXV_BALANCE_B_S_BALANCE = -fillB.S newState.TXV_BALANCE_A_S_ADDRESS = ring.orderB.tokenB newState.TXV_BALANCE_B_B_BALANCE = fillB.B - fee_B newState.TXV_STORAGE_B_ADDRESS = ring.orderB.storageID newState.TXV_STORAGE_B_DATA = filled_B + (fillB.B if ring.orderB.fillAmountBorS else fillB.S) newState.TXV_STORAGE_B_STORAGEID = ring.orderB.storageID newState.balanceDeltaA_O = fee_A - protocolFee_A newState.balanceDeltaB_O = fee_B - protocolFee_B newState.balanceDeltaA_P = protocolFee_A newState.balanceDeltaB_P = protocolFee_B elif txInput.txType == "Transfer": storageData = self.getData(txInput.fromAccountID, txInput.tokenID, txInput.storageID) transferAmount = roundToFloatValue(int(txInput.amount), Float24Encoding) feeValue = roundToFloatValue(int(txInput.fee), Float16Encoding) newState.signatureA = txInput.signature newState.signatureB = txInput.dualSignature newState.TXV_ACCOUNT_A_ADDRESS = txInput.fromAccountID accountA = self.getAccount(newState.TXV_ACCOUNT_A_ADDRESS) newState.TXV_BALANCE_A_S_ADDRESS = txInput.tokenID newState.TXV_BALANCE_A_S_BALANCE = -transferAmount newState.TXV_BALANCE_B_S_ADDRESS = txInput.feeTokenID newState.TXV_BALANCE_A_B_BALANCE = -feeValue newState.TXV_ACCOUNT_B_ADDRESS = txInput.toAccountID accountB = self.getAccount(newState.TXV_ACCOUNT_B_ADDRESS) newState.TXV_ACCOUNT_B_OWNER = txInput.to newState.TXV_BALANCE_A_S_ADDRESS = txInput.tokenID newState.TXV_BALANCE_B_B_BALANCE = transferAmount newState.TXV_STORAGE_A_ADDRESS = txInput.storageID newState.TXV_STORAGE_A_DATA = 1 newState.TXV_STORAGE_A_STORAGEID = txInput.storageID if txInput.type != 0: context.numConditionalTransactions = context.numConditionalTransactions + 1 newState.balanceDeltaA_O = feeValue # For tests (used to set the DA data) txInput.toNewAccount = True if accountB.owner == str(0) else False elif txInput.txType == "Withdraw": ## calculate how much can be withdrawn account = self.getAccount(txInput.accountID) if int(txInput.type) == 2: # Full balance with intrest balanceLeaf = account.getBalanceLeaf(txInput.tokenID) txInput.amount = str(balanceLeaf.balance) elif int(txInput.type) == 3: txInput.amount = str(0) # Protocol fee withdrawals are handled a bit differently # as the balance needs to be withdrawn from the already opened protocol pool account isProtocolfeeWithdrawal = int(txInput.accountID) == 0 feeValue = roundToFloatValue(int(txInput.fee), Float16Encoding) newState.signatureA = txInput.signature newState.TXV_ACCOUNT_A_ADDRESS = 1 if isProtocolfeeWithdrawal else txInput.accountID accountA = self.getAccount(newState.TXV_ACCOUNT_A_ADDRESS) newState.TXV_BALANCE_A_S_ADDRESS = txInput.tokenID newState.TXV_BALANCE_A_S_BALANCE = 0 if isProtocolfeeWithdrawal else -int(txInput.amount) newState.TXV_BALANCE_B_S_ADDRESS = txInput.feeTokenID newState.TXV_BALANCE_A_B_BALANCE = -feeValue newState.TXV_STORAGE_A_ADDRESS = txInput.storageID if int(txInput.type) == 0 or int(txInput.type) == 1: newState.TXV_STORAGE_A_DATA = 1 newState.TXV_STORAGE_A_STORAGEID = txInput.storageID if not isProtocolfeeWithdrawal and int(txInput.type) == 2: newState.TXV_BALANCE_A_S_WEIGHT = 0 newState.balanceDeltaA_O = feeValue newState.balanceDeltaB_P = -int(txInput.amount) if isProtocolfeeWithdrawal else 0 context.numConditionalTransactions = context.numConditionalTransactions + 1 elif txInput.txType == "Deposit": newState.TXV_ACCOUNT_A_ADDRESS = txInput.accountID newState.TXV_ACCOUNT_A_OWNER = txInput.owner newState.TXV_BALANCE_A_S_ADDRESS = txInput.tokenID newState.TXV_BALANCE_A_S_BALANCE = txInput.amount context.numConditionalTransactions = context.numConditionalTransactions + 1 elif txInput.txType == "AccountUpdate": feeValue = roundToFloatValue(int(txInput.fee), Float16Encoding) newState.TXV_ACCOUNT_A_ADDRESS = txInput.accountID accountA = self.getAccount(newState.TXV_ACCOUNT_A_ADDRESS) newState.TXV_ACCOUNT_A_PUBKEY_X = txInput.publicKeyX newState.TXV_ACCOUNT_A_PUBKEY_Y = txInput.publicKeyY newState.TXV_ACCOUNT_A_NONCE = 1 newState.TXV_BALANCE_A_S_ADDRESS = txInput.feeTokenID newState.TXV_BALANCE_A_S_BALANCE = -feeValue newState.balanceDeltaB_O = feeValue newState.signatureA = txInput.signature if txInput.type != 0: context.numConditionalTransactions = context.numConditionalTransactions + 1 elif txInput.txType == "AmmUpdate": # Cache the balance for tests account = self.getAccount(txInput.accountID) balanceLeaf = account.getBalanceLeaf(txInput.tokenID) txInput.balance = str(balanceLeaf.balance) newState.TXV_ACCOUNT_A_ADDRESS = txInput.accountID newState.TXV_BALANCE_A_S_ADDRESS = txInput.tokenID newState.TXV_ACCOUNT_A_NONCE = 1 newState.TXV_ACCOUNT_A_FEEBIPSAMM = txInput.feeBips newState.TXV_BALANCE_A_S_WEIGHT = txInput.tokenWeight context.numConditionalTransactions = context.numConditionalTransactions + 1 # Tokens default values newState.TXV_BALANCE_A_S_ADDRESS = setValue(newState.TXV_BALANCE_A_S_ADDRESS, 0) newState.TXV_BALANCE_B_S_ADDRESS = setValue(newState.TXV_BALANCE_B_S_ADDRESS, 0) # A default values newState.TXV_ACCOUNT_A_ADDRESS = setValue(newState.TXV_ACCOUNT_A_ADDRESS, 1) accountA = self.getAccount(newState.TXV_ACCOUNT_A_ADDRESS) newState.TXV_ACCOUNT_A_OWNER = setValue(newState.TXV_ACCOUNT_A_OWNER, accountA.owner) newState.TXV_ACCOUNT_A_PUBKEY_X = setValue(newState.TXV_ACCOUNT_A_PUBKEY_X, accountA.publicKeyX) newState.TXV_ACCOUNT_A_PUBKEY_Y = setValue(newState.TXV_ACCOUNT_A_PUBKEY_Y, accountA.publicKeyY) newState.TXV_ACCOUNT_A_NONCE = setValue(newState.TXV_ACCOUNT_A_NONCE, 0) newState.TXV_ACCOUNT_A_FEEBIPSAMM = setValue(newState.TXV_ACCOUNT_A_FEEBIPSAMM, accountA.feeBipsAMM) balanceLeafA_S = accountA.getBalanceLeaf(newState.TXV_BALANCE_A_S_ADDRESS) newState.TXV_BALANCE_A_S_BALANCE = setValue(newState.TXV_BALANCE_A_S_BALANCE, 0) newState.TXV_BALANCE_A_S_WEIGHT = setValue(newState.TXV_BALANCE_A_S_WEIGHT, balanceLeafA_S.weightAMM) newState.TXV_BALANCE_A_B_BALANCE = setValue(newState.TXV_BALANCE_A_B_BALANCE, 0) newState.TXV_STORAGE_A_ADDRESS = setValue(newState.TXV_STORAGE_A_ADDRESS, 0) storageA = balanceLeafA_S.getStorage(newState.TXV_STORAGE_A_ADDRESS) newState.TXV_STORAGE_A_DATA = setValue(newState.TXV_STORAGE_A_DATA, storageA.data) newState.TXV_STORAGE_A_STORAGEID = setValue(newState.TXV_STORAGE_A_STORAGEID, storageA.storageID) # Operator default values newState.balanceDeltaA_O = setValue(newState.balanceDeltaA_O, 0) newState.balanceDeltaB_O = setValue(newState.balanceDeltaB_O, 0) # Protocol fees default values newState.balanceDeltaA_P = setValue(newState.balanceDeltaA_P, 0) newState.balanceDeltaB_P = setValue(newState.balanceDeltaB_P, 0) # Copy the initial merkle root accountsMerkleRoot = self._accountsTree._root # Update A accountA = self.getAccount(newState.TXV_ACCOUNT_A_ADDRESS) rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(newState.TXV_ACCOUNT_A_ADDRESS)) proof = self._accountsTree.createProof(newState.TXV_ACCOUNT_A_ADDRESS) (balanceUpdateS_A, storageUpdate_A) = accountA.updateBalanceAndStorage( newState.TXV_BALANCE_A_S_ADDRESS, newState.TXV_STORAGE_A_STORAGEID, newState.TXV_STORAGE_A_DATA, newState.TXV_BALANCE_A_S_BALANCE, newState.TXV_BALANCE_A_S_WEIGHT ) balanceUpdateB_A = accountA.updateBalance( newState.TXV_BALANCE_B_S_ADDRESS, newState.TXV_BALANCE_A_B_BALANCE ) accountA.owner = newState.TXV_ACCOUNT_A_OWNER accountA.publicKeyX = newState.TXV_ACCOUNT_A_PUBKEY_X accountA.publicKeyY = newState.TXV_ACCOUNT_A_PUBKEY_Y accountA.nonce = accountA.nonce + newState.TXV_ACCOUNT_A_NONCE accountA.feeBipsAMM = newState.TXV_ACCOUNT_A_FEEBIPSAMM self.updateAccountTree(newState.TXV_ACCOUNT_A_ADDRESS) accountAfter = copyAccountInfo(self.getAccount(newState.TXV_ACCOUNT_A_ADDRESS)) rootAfter = self._accountsTree._root accountUpdate_A = AccountUpdateData(newState.TXV_ACCOUNT_A_ADDRESS, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # B default values newState.TXV_ACCOUNT_B_ADDRESS = setValue(newState.TXV_ACCOUNT_B_ADDRESS, 1) accountB = self.getAccount(newState.TXV_ACCOUNT_B_ADDRESS) newState.TXV_ACCOUNT_B_OWNER = setValue(newState.TXV_ACCOUNT_B_OWNER, accountB.owner) newState.TXV_ACCOUNT_B_PUBKEY_X = setValue(newState.TXV_ACCOUNT_B_PUBKEY_X, accountB.publicKeyX) newState.TXV_ACCOUNT_B_PUBKEY_Y = setValue(newState.TXV_ACCOUNT_B_PUBKEY_Y, accountB.publicKeyY) newState.TXV_ACCOUNT_B_NONCE = setValue(newState.TXV_ACCOUNT_B_NONCE, 0) balanceLeafB_S = accountB.getBalanceLeaf(newState.TXV_BALANCE_B_S_ADDRESS) newState.TXV_BALANCE_B_S_BALANCE = setValue(newState.TXV_BALANCE_B_S_BALANCE, 0) newState.TXV_BALANCE_B_B_BALANCE = setValue(newState.TXV_BALANCE_B_B_BALANCE, 0) newState.TXV_STORAGE_B_ADDRESS = setValue(newState.TXV_STORAGE_B_ADDRESS, 0) storageB = balanceLeafB_S.getStorage(newState.TXV_STORAGE_B_ADDRESS) newState.TXV_STORAGE_B_DATA = setValue(newState.TXV_STORAGE_B_DATA, storageB.data) newState.TXV_STORAGE_B_STORAGEID = setValue(newState.TXV_STORAGE_B_STORAGEID, storageB.storageID) # Update B accountB = self.getAccount(newState.TXV_ACCOUNT_B_ADDRESS) rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(newState.TXV_ACCOUNT_B_ADDRESS)) proof = self._accountsTree.createProof(newState.TXV_ACCOUNT_B_ADDRESS) (balanceUpdateS_B, storageUpdate_B) = accountB.updateBalanceAndStorage( newState.TXV_BALANCE_B_S_ADDRESS, newState.TXV_STORAGE_B_STORAGEID, newState.TXV_STORAGE_B_DATA, newState.TXV_BALANCE_B_S_BALANCE ) balanceUpdateB_B = accountB.updateBalance( newState.TXV_BALANCE_A_S_ADDRESS, newState.TXV_BALANCE_B_B_BALANCE ) accountB.owner = newState.TXV_ACCOUNT_B_OWNER accountB.publicKeyX = newState.TXV_ACCOUNT_B_PUBKEY_X accountB.publicKeyY = newState.TXV_ACCOUNT_B_PUBKEY_Y accountB.nonce = accountB.nonce + newState.TXV_ACCOUNT_B_NONCE self.updateAccountTree(newState.TXV_ACCOUNT_B_ADDRESS) accountAfter = copyAccountInfo(self.getAccount(newState.TXV_ACCOUNT_B_ADDRESS)) rootAfter = self._accountsTree._root accountUpdate_B = AccountUpdateData(newState.TXV_ACCOUNT_B_ADDRESS, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Update balances Operator accountO = self.getAccount(context.operatorAccountID) rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(context.operatorAccountID)) proof = self._accountsTree.createProof(context.operatorAccountID) balanceUpdateB_O = accountO.updateBalance( newState.TXV_BALANCE_A_S_ADDRESS, newState.balanceDeltaB_O ) balanceUpdateA_O = accountO.updateBalance( newState.TXV_BALANCE_B_S_ADDRESS, newState.balanceDeltaA_O ) self.updateAccountTree(context.operatorAccountID) accountAfter = copyAccountInfo(self.getAccount(context.operatorAccountID)) rootAfter = self._accountsTree._root accountUpdate_O = AccountUpdateData(context.operatorAccountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Protocol fee payment balanceUpdateB_P = self.getAccount(0).updateBalance(newState.TXV_BALANCE_A_S_ADDRESS, newState.balanceDeltaB_P) balanceUpdateA_P = self.getAccount(0).updateBalance(newState.TXV_BALANCE_B_S_ADDRESS, newState.balanceDeltaA_P) ### witness = Witness(newState.signatureA, newState.signatureB, accountsMerkleRoot, storageUpdate_A, storageUpdate_B, balanceUpdateS_A, balanceUpdateB_A, accountUpdate_A, balanceUpdateS_B, balanceUpdateB_B, accountUpdate_B, balanceUpdateA_O, balanceUpdateB_O, accountUpdate_O, balanceUpdateA_P, balanceUpdateB_P) return TxWitness(witness, txInput) def getAccount(self, accountID): # Make sure the leaf exist in our map if not(str(accountID) in self._accounts): # print("Account doesn't exist: " + str(accountID)) self._accounts[str(accountID)] = Account(0, Point(0, 0)) return self._accounts[str(accountID)] def updateAccountTree(self, accountID): self._accountsTree.update(accountID, self.getAccount(accountID).hash()) def getRoot(self): return self._accountsTree._root
class Account(object): def __init__(self, owner, publicKey): self.owner = str(owner) self.publicKeyX = str(publicKey.x) self.publicKeyY = str(publicKey.y) self.nonce = 0 self.feeBipsAMM = 0 # Balances self._balancesTree = SparseMerkleTree(BINARY_TREE_DEPTH_TOKENS // 2, 4) self._balancesTree.newTree(BalanceLeaf().hash()) self._balancesLeafs = {} # print("Empty balances tree: " + str(self._balancesTree._root)) def hash(self): return poseidon([int(self.owner), int(self.publicKeyX), int(self.publicKeyY), int(self.nonce), int(self.feeBipsAMM), int(self._balancesTree._root)], poseidonParamsAccount) def fromJSON(self, jAccount): self.owner = jAccount["owner"] self.publicKeyX = jAccount["publicKeyX"] self.publicKeyY = jAccount["publicKeyY"] self.nonce = int(jAccount["nonce"]) self.feeBipsAMM = int(jAccount["feeBipsAMM"]) # Balances balancesLeafsDict = jAccount["_balancesLeafs"] for key, val in balancesLeafsDict.items(): balanceLeaf = BalanceLeaf() balanceLeaf.fromJSON(val) self._balancesLeafs[key] = balanceLeaf self._balancesTree._root = jAccount["_balancesTree"]["_root"] self._balancesTree._db.kv = jAccount["_balancesTree"]["_db"]["kv"] def getBalanceLeaf(self, address): # Make sure the leaf exist in our map if not(str(address) in self._balancesLeafs): return BalanceLeaf() else: return self._balancesLeafs[str(address)] def getBalance(self, address): return self.getBalanceLeaf(address).balance def updateBalance(self, tokenID, deltaBalance): # Make sure the leaf exists in our map if not(str(tokenID) in self._balancesLeafs): self._balancesLeafs[str(tokenID)] = BalanceLeaf() balancesBefore = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) rootBefore = self._balancesTree._root self._balancesLeafs[str(tokenID)].balance = str(int(self._balancesLeafs[str(tokenID)].balance) + int(deltaBalance)) balancesAfter = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) proof = self._balancesTree.createProof(tokenID) self._balancesTree.update(tokenID, self._balancesLeafs[str(tokenID)].hash()) rootAfter = self._balancesTree._root return BalanceUpdateData(tokenID, proof, rootBefore, rootAfter, balancesBefore, balancesAfter) def updateBalanceAndStorage(self, tokenID, storageID, filled, delta_balance, weight = None): # Make sure the leaf exist in our map if not(str(tokenID) in self._balancesLeafs): self._balancesLeafs[str(tokenID)] = BalanceLeaf() balancesBefore = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) rootBefore = self._balancesTree._root # Update filled amounts storageUpdate = self._balancesLeafs[str(tokenID)].updateStorage(storageID, filled) self._balancesLeafs[str(tokenID)].balance = str(int(self._balancesLeafs[str(tokenID)].balance) + int(delta_balance)) if weight is not None: self._balancesLeafs[str(tokenID)].weightAMM = str(weight) #print("str(delta_balance): " + str(delta_balance)) #print("endBalance: " + self._balancesLeafs[str(tokenID)].balance) balancesAfter = copyBalanceInfo(self._balancesLeafs[str(tokenID)]) proof = self._balancesTree.createProof(tokenID) self._balancesTree.update(tokenID, self._balancesLeafs[str(tokenID)].hash()) rootAfter = self._balancesTree._root return (BalanceUpdateData(tokenID, proof, rootBefore, rootAfter, balancesBefore, balancesAfter), storageUpdate)
class State(object): def __init__(self, realmID): self.realmID = int(realmID) # Accounts self._accountsTree = SparseMerkleTree(TREE_DEPTH_ACCOUNTS) self._accountsTree.newTree(getDefaultAccount().hash()) self._accounts = {} self._accounts[str(0)] = getDefaultAccount() # print("Empty accounts tree: " + str(hex(self._accountsTree._root))) def load(self, filename): with open(filename) as f: data = json.load(f) self.realmID = int(data["realmID"]) # Accounts accountLeafsDict = data["accounts_values"] for key, val in accountLeafsDict.items(): account = getDefaultAccount() account.fromJSON(val) self._accounts[key] = account self._accountsTree._root = data["accounts_root"] self._accountsTree._db.kv = data["accounts_tree"] def save(self, filename): with open(filename, "w") as file: file.write( json.dumps( { "realmID": self.realmID, "accounts_values": self._accounts, "accounts_root": self._accountsTree._root, "accounts_tree": self._accountsTree._db.kv, }, default=lambda o: o.__dict__, sort_keys=True, indent=4)) def calculateFees(self, fee, walletSplitPercentage, waiveFeePercentage): walletFee = (fee * walletSplitPercentage) // 100 matchingFee = fee - walletFee matchingFeeAfterWaiving = (matchingFee * waiveFeePercentage) // 100 return (walletFee, matchingFeeAfterWaiving) def getMaxFillAmounts(self, order): account = self.getAccount(order.accountID) tradeHistory = account.getBalanceLeaf(order.tokenS).getTradeHistory( int(order.orderID)) order.tradeHistoryFilled = str(tradeHistory.filled) order.tradeHistoryCancelled = int(tradeHistory.cancelled) order.tradeHistoryOrderID = int(tradeHistory.orderID) order.nonce = int(account.nonce) order.balanceS = str(account.getBalance(order.tokenS)) order.balanceB = str(account.getBalance(order.tokenB)) order.balanceF = str(account.getBalance(order.tokenF)) # Trade history trimming bNew = tradeHistory.orderID < order.orderID bTrim = not (tradeHistory.orderID <= order.orderID) filled = 0 if bNew else int(tradeHistory.filled) cancelledToStore = 0 if bNew else int(tradeHistory.cancelled) cancelled = 1 if bTrim else cancelledToStore orderIDToStore = int(order.orderID) if bNew else tradeHistory.orderID """ print("bNew: " + str(bNew)) print("bTrim: " + str(bTrim)) print("filled: " + str(filled)) print("cancelledToStore: " + str(cancelledToStore)) print("cancelled: " + str(cancelled)) print("orderIDToStore: " + str(orderIDToStore)) """ # Scale the order balanceS = int(account.getBalance(order.tokenS)) balanceF = int(account.getBalance(order.tokenF)) remainingS = int(order.amountS) - filled if cancelled == 1: remainingS = 0 fillAmountS = balanceS if (balanceS < remainingS) else remainingS # Check how much fee needs to be paid. We limit fillAmountS to how much # fee the order owner can pay. fillAmountF = int(order.amountF) * fillAmountS // int(order.amountS) if order.tokenF == order.tokenS and balanceS < fillAmountS + fillAmountF: # Equally divide the available tokens between fillAmountS and fillAmountF fillAmountS = balanceS * int( order.amountS) // (int(order.amountS) + int(order.amountF)) if order.tokenF != order.tokenS and balanceF < fillAmountF: # Scale down fillAmountS so the available fillAmountF is sufficient fillAmountS = balanceF * int(order.amountS) // int(order.amountF) if order.tokenF == order.tokenB and int(order.amountF) <= int( order.amountB): # No rebalancing (because of insufficient balanceF) is ever necessary when amountF <= amountB fillAmountS = balanceS if (balanceS < remainingS) else remainingS fillAmountB = (fillAmountS * int(order.amountB)) // int(order.amountS) return (fillAmountS, fillAmountB, filled, cancelledToStore, orderIDToStore) def settleRing(self, context, ring): #print("State update ring: ") (fillAmountS_A, fillAmountB_A, filled_A, cancelledToStore_A, orderIDToStore_A) = self.getMaxFillAmounts(ring.orderA) (fillAmountS_B, fillAmountB_B, filled_B, cancelledToStore_B, orderIDToStore_B) = self.getMaxFillAmounts(ring.orderB) ''' print("fillAmountS_A: " + str(fillAmountS_A)) print("fillAmountB_A: " + str(fillAmountB_A)) print("fillAmountS_B: " + str(fillAmountS_B)) print("fillAmountB_B: " + str(fillAmountB_B)) print("-------------") ''' if fillAmountB_A < fillAmountS_B: fillAmountS_B = fillAmountB_A fillAmountB_B = (fillAmountS_B * int(ring.orderB.amountB)) // int( ring.orderB.amountS) else: fillAmountB_A = fillAmountS_B fillAmountS_A = (fillAmountB_A * int(ring.orderA.amountS)) // int( ring.orderA.amountB) fillAmountF_A = (int(ring.orderA.amountF) * fillAmountS_A) // int( ring.orderA.amountS) fillAmountF_B = (int(ring.orderB.amountF) * fillAmountS_B) // int( ring.orderB.amountS) margin = fillAmountS_A - fillAmountB_B # matchable ring.valid = True if fillAmountS_A < fillAmountB_B: ring.valid = False # self-trading totalFee = fillAmountF_A + fillAmountF_B if ring.orderA.accountID == ring.orderB.accountID and ring.orderA.tokenF == ring.orderB.tokenF and int( ring.orderA.balanceF) < totalFee: ring.valid = False ring.orderA.checkValid(context, fillAmountS_A, fillAmountB_A) ring.orderB.checkValid(context, fillAmountS_B, fillAmountB_B) ring.valid = ring.valid and ring.orderA.valid and ring.orderB.valid #print("ring.orderA.valid " + str(ring.orderA.valid)) #print("ring.orderB.valid " + str(ring.orderB.valid)) if ring.valid == False: #print("ring.valid false: ") fillAmountS_A = 0 fillAmountB_A = 0 fillAmountF_A = 0 fillAmountS_B = 0 fillAmountB_B = 0 fillAmountF_B = 0 margin = 0 ring.fillS_A = str(fillAmountS_A) ring.fillB_A = str(fillAmountB_A) ring.fillF_A = str(fillAmountF_A) ring.fillS_B = str(fillAmountS_B) ring.fillB_B = str(fillAmountB_B) ring.fillF_B = str(fillAmountF_B) ring.margin = str(margin) ''' print("fillAmountS_A: " + str(fillAmountS_A)) print("fillAmountB_A: " + str(fillAmountB_A)) print("fillAmountF_A: " + str(fillAmountF_A)) print("fillAmountS_B: " + str(fillAmountS_B)) print("fillAmountB_B: " + str(fillAmountB_B)) print("fillAmountF_B: " + str(fillAmountF_B)) print("margin: " + str(margin)) ''' # Copy the initial merkle root accountsMerkleRoot = self._accountsTree._root (walletFee_A, matchingFee_A) = self.calculateFees(int(ring.fillF_A), ring.orderA.walletSplitPercentage, ring.orderA.waiveFeePercentage) (walletFee_B, matchingFee_B) = self.calculateFees(int(ring.fillF_B), ring.orderB.walletSplitPercentage, ring.orderB.waiveFeePercentage) # Update balances A accountA = self.getAccount(ring.orderA.accountID) rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(ring.orderA.accountID)) proof = self._accountsTree.createProof(ring.orderA.accountID) (balanceUpdateS_A, tradeHistoryUpdate_A) = accountA.updateBalanceAndTradeHistory( ring.orderA.tokenS, ring.orderA.orderID, -int(ring.fillS_A), filled_A + int(ring.fillS_A), cancelledToStore_A, orderIDToStore_A) balanceUpdateB_A = accountA.updateBalance(ring.orderA.tokenB, int(ring.fillB_A)) balanceUpdateF_A = accountA.updateBalance( ring.orderA.tokenF, -(walletFee_A + matchingFee_A)) self.updateAccountTree(ring.orderA.accountID) accountAfter = copyAccountInfo(self.getAccount(ring.orderA.accountID)) rootAfter = self._accountsTree._root accountUpdate_A = AccountUpdateData(ring.orderA.accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Update balances B accountB = self.getAccount(ring.orderB.accountID) ring.orderB.balanceS = str(accountB.getBalance(ring.orderB.tokenS)) ring.orderB.balanceB = str(accountB.getBalance(ring.orderB.tokenB)) ring.orderB.balanceF = str(accountB.getBalance(ring.orderB.tokenF)) rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(ring.orderB.accountID)) proof = self._accountsTree.createProof(ring.orderB.accountID) (balanceUpdateS_B, tradeHistoryUpdate_B) = accountB.updateBalanceAndTradeHistory( ring.orderB.tokenS, ring.orderB.orderID, -int(ring.fillS_B), filled_B + int(ring.fillS_B), cancelledToStore_B, orderIDToStore_B) balanceUpdateB_B = accountB.updateBalance(ring.orderB.tokenB, int(ring.fillB_B)) balanceUpdateF_B = accountB.updateBalance( ring.orderB.tokenF, -(walletFee_B + matchingFee_B)) self.updateAccountTree(ring.orderB.accountID) accountAfter = copyAccountInfo(self.getAccount(ring.orderB.accountID)) rootAfter = self._accountsTree._root accountUpdate_B = AccountUpdateData(ring.orderB.accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Update wallet A rootBefore = self._accountsTree._root accountBefore = copyAccountInfo( self.getAccount(ring.orderA.walletAccountID)) proof = self._accountsTree.createProof(ring.orderA.walletAccountID) balanceUpdateA_W = self.getAccount( ring.orderA.walletAccountID).updateBalance(ring.orderA.tokenF, walletFee_A) self.updateAccountTree(ring.orderA.walletAccountID) accountAfter = copyAccountInfo( self.getAccount(ring.orderA.walletAccountID)) rootAfter = self._accountsTree._root accountUpdateA_W = AccountUpdateData(ring.orderA.walletAccountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Update wallet B rootBefore = self._accountsTree._root accountBefore = copyAccountInfo( self.getAccount(ring.orderB.walletAccountID)) proof = self._accountsTree.createProof(ring.orderB.walletAccountID) balanceUpdateB_W = self.getAccount( ring.orderB.walletAccountID).updateBalance(ring.orderB.tokenF, walletFee_B) self.updateAccountTree(ring.orderB.walletAccountID) accountAfter = copyAccountInfo( self.getAccount(ring.orderB.walletAccountID)) rootAfter = self._accountsTree._root accountUpdateB_W = AccountUpdateData(ring.orderB.walletAccountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Update feeRecipient rootBefore = self._accountsTree._root accountBefore = copyAccountInfo( self.getAccount(ring.feeRecipientAccountID)) proof = self._accountsTree.createProof(ring.feeRecipientAccountID) balanceUpdateA_F = self.getAccount( ring.feeRecipientAccountID).updateBalance(ring.orderA.tokenF, matchingFee_A) balanceUpdateB_F = self.getAccount( ring.feeRecipientAccountID).updateBalance(ring.orderB.tokenF, matchingFee_B) self.updateAccountTree(ring.feeRecipientAccountID) accountAfter = copyAccountInfo( self.getAccount(ring.feeRecipientAccountID)) rootAfter = self._accountsTree._root accountUpdate_F = AccountUpdateData(ring.feeRecipientAccountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) # Update ringmatcher accountM = self.getAccount(ring.minerAccountID) rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(ring.minerAccountID)) proof = self._accountsTree.createProof(ring.minerAccountID) balanceUpdateM_M = accountM.updateBalance(ring.orderA.tokenS, int(ring.margin)) balanceUpdateO_M = accountM.updateBalance(ring.tokenID, -int(ring.fee)) accountM.nonce += 1 self.updateAccountTree(ring.minerAccountID) accountAfter = copyAccountInfo(self.getAccount(ring.minerAccountID)) rootAfter = self._accountsTree._root accountUpdate_M = AccountUpdateData(ring.minerAccountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Operator payment balanceUpdateF_O = self.getAccount( context.operatorAccountID).updateBalance(ring.tokenID, int(ring.fee)) ### return RingSettlement( ring, accountsMerkleRoot, tradeHistoryUpdate_A, tradeHistoryUpdate_B, balanceUpdateS_A, balanceUpdateB_A, balanceUpdateF_A, accountUpdate_A, balanceUpdateS_B, balanceUpdateB_B, balanceUpdateF_B, accountUpdate_B, balanceUpdateA_W, accountUpdateA_W, balanceUpdateB_W, accountUpdateB_W, balanceUpdateA_F, balanceUpdateB_F, accountUpdate_F, balanceUpdateM_M, balanceUpdateO_M, accountUpdate_M, balanceUpdateF_O, walletFee_A, matchingFee_A, walletFee_B, matchingFee_B) def deposit(self, accountID, secretKey, publicKeyX, publicKeyY, token, amount): # Copy the initial merkle root rootBefore = self._accountsTree._root if not (str(accountID) in self._accounts): accountBefore = copyAccountInfo(getDefaultAccount()) else: accountBefore = copyAccountInfo(self.getAccount(accountID)) proof = self._accountsTree.createProof(accountID) # Create the account if necessary if not (str(accountID) in self._accounts): self._accounts[str(accountID)] = Account( secretKey, Point(publicKeyX, publicKeyY)) account = self.getAccount(accountID) balanceUpdate = account.updateBalance(token, amount) # Update keys account.secretKey = str(secretKey) account.publicKeyX = str(publicKeyX) account.publicKeyY = str(publicKeyY) self._accountsTree.update(accountID, account.hash()) accountAfter = copyAccountInfo(account) rootAfter = self._accountsTree._root accountUpdate = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) return Deposit(amount, balanceUpdate, accountUpdate) def getAccount(self, accountID): # Make sure the leaf exist in our map if not (str(accountID) in self._accounts): print("Account doesn't exist: " + str(accountID)) return self._accounts[str(accountID)] def onchainWithdraw(self, realmID, accountID, tokenID, amountRequested, shutdown): # When a withdrawal is done before the deposit (account creation) we shouldn't # do anything. Just leave everything as it is. if str(accountID) in self._accounts: # Calculate amount withdrawn balance = int(self.getAccount(accountID).getBalance(tokenID)) amount = int(amountRequested) if ( int(amountRequested) < balance) else balance # Update account rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(accountID)) proof = self._accountsTree.createProof(accountID) balanceUpdate = self.getAccount(accountID).updateBalance( tokenID, -amount, shutdown) if shutdown: self.getAccount(accountID).publicKeyX = str(0) self.getAccount(accountID).publicKeyY = str(0) self.getAccount(accountID).nonce = 0 self.updateAccountTree(accountID) accountAfter = copyAccountInfo(self.getAccount(accountID)) rootAfter = self._accountsTree._root accountUpdate = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### else: # Dummy update amount = 0 rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(getDefaultAccount()) proof = self._accountsTree.createProof(accountID) balanceUpdate = getDefaultAccount().updateBalance( tokenID, 0, shutdown) accountAfter = copyAccountInfo(getDefaultAccount()) rootAfter = self._accountsTree._root accountUpdate = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### withdrawal = OnchainWithdrawal(amountRequested, balanceUpdate, accountUpdate, accountID, tokenID, amount) return withdrawal def offchainWithdraw(self, realmID, accountID, tokenID, amountRequested, operatorAccountID, walletAccountID, feeTokenID, fee, walletSplitPercentage): feeToWallet = int(fee) * walletSplitPercentage // 100 feeToOperator = int(fee) - feeToWallet # Update account rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(accountID)) nonce = accountBefore.nonce proof = self._accountsTree.createProof(accountID) balanceUpdateF_A = self.getAccount(accountID).updateBalance( feeTokenID, -fee) balance = int(self.getAccount(accountID).getBalance(tokenID)) amountWithdrawn = int(amountRequested) if ( int(amountRequested) < balance) else balance balanceUpdateW_A = self.getAccount(accountID).updateBalance( tokenID, -amountWithdrawn) self.getAccount(accountID).nonce += 1 self.updateAccountTree(accountID) accountAfter = copyAccountInfo(self.getAccount(accountID)) rootAfter = self._accountsTree._root accountUpdate_A = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Update wallet rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(walletAccountID)) proof = self._accountsTree.createProof(walletAccountID) balanceUpdateF_W = self.getAccount(walletAccountID).updateBalance( feeTokenID, feeToWallet) self.updateAccountTree(walletAccountID) accountAfter = copyAccountInfo(self.getAccount(walletAccountID)) rootAfter = self._accountsTree._root accountUpdate_W = AccountUpdateData(walletAccountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Operator payment balanceUpdateF_O = self.getAccount(operatorAccountID).updateBalance( feeTokenID, feeToOperator) account = self.getAccount(accountID) withdrawal = OffchainWithdrawal( realmID, accountID, tokenID, amountRequested, amountWithdrawn, walletAccountID, feeTokenID, fee, walletSplitPercentage, balanceUpdateF_A, balanceUpdateW_A, accountUpdate_A, balanceUpdateF_W, accountUpdate_W, balanceUpdateF_O, nonce) withdrawal.sign(FQ(int(account.secretKey))) return withdrawal def cancelOrder(self, realmID, accountID, orderTokenID, orderID, walletAccountID, operatorAccountID, feeTokenID, fee, walletSplitPercentage): feeToWallet = int(fee) * walletSplitPercentage // 100 feeToOperator = int(fee) - feeToWallet # Update account rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(accountID)) nonce = accountBefore.nonce proof = self._accountsTree.createProof(accountID) (balanceUpdateT_A, tradeHistoryUpdate_A) = self.getAccount(accountID).cancelOrder( orderTokenID, orderID) balanceUpdateF_A = self.getAccount(accountID).updateBalance( feeTokenID, -fee) self.getAccount(accountID).nonce += 1 self.updateAccountTree(accountID) accountAfter = copyAccountInfo(self.getAccount(accountID)) rootAfter = self._accountsTree._root accountUpdate_A = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Update wallet rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(walletAccountID)) proof = self._accountsTree.createProof(walletAccountID) balanceUpdateF_W = self.getAccount(walletAccountID).updateBalance( feeTokenID, feeToWallet) self.updateAccountTree(walletAccountID) accountAfter = copyAccountInfo(self.getAccount(walletAccountID)) rootAfter = self._accountsTree._root accountUpdate_W = AccountUpdateData(walletAccountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Operator payment balanceUpdateF_O = self.getAccount(operatorAccountID).updateBalance( feeTokenID, feeToOperator) account = self.getAccount(accountID) walletAccount = self.getAccount(walletAccountID) cancellation = Cancellation( Point(int(account.publicKeyX), int(account.publicKeyY)), Point(int(walletAccount.publicKeyX), int(walletAccount.publicKeyY)), realmID, accountID, orderTokenID, orderID, walletAccountID, operatorAccountID, feeTokenID, fee, walletSplitPercentage, nonce, tradeHistoryUpdate_A, balanceUpdateT_A, balanceUpdateF_A, accountUpdate_A, balanceUpdateF_W, accountUpdate_W, balanceUpdateF_O) cancellation.sign(FQ(int(account.secretKey))) return cancellation def createWithdrawProof(self, realmID, accountID, tokenID): account = copyAccountInfo(self.getAccount(accountID)) balance = copyBalanceInfo( self.getAccount(accountID)._balancesLeafs[str(tokenID)]) accountProof = self._accountsTree.createProof(accountID) balanceProof = self.getAccount(accountID)._balancesTree.createProof( tokenID) return WithdrawProof(realmID, accountID, tokenID, account, balance, self.getRoot(), accountProof, balanceProof) def updateAccountTree(self, accountID): self._accountsTree.update(accountID, self.getAccount(accountID).hash()) def getRoot(self): return self._accountsTree._root