diff --git a/.gitignore b/.gitignore
index 1159478e..dade502e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,7 @@ x64/
/test/universe.*
/test/contract0???.???
/test/tmp_file_*
+
+.DS_Store
+.clang-format
+tmp
\ No newline at end of file
diff --git a/LICENSE-MIT.md b/LICENSE-MIT.md
new file mode 100644
index 00000000..08bd0ec5
--- /dev/null
+++ b/LICENSE-MIT.md
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 - 2017 Jason Lee @ calccrypto at gmail.com
+
+Applies to the files src/platform/uint128.h and test/uint128.cpp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
index 60cc96d2..b8723f5e 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -39,3 +39,10 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+This project is licensed under the Anti-Military License. However, it includes
+some code licensed under the MIT License. The MIT licensed code is used with
+permission and retains its original MIT license.
+
+The MIT licensed code is from the following sources:
+- [uint128_t](https://github.com/calccrypto/uint128_t)
\ No newline at end of file
diff --git a/README.md b/README.md
index 0cf28819..be078e19 100644
--- a/README.md
+++ b/README.md
@@ -147,6 +147,13 @@ We recommend to synchronize a Linux / Windows / MacOS machine with NTP permanent
## License
The Anti-Military License. See [LICENSE.md](LICENSE.md).
+This project is licensed under the Anti-Military License. However, it includes
+some code licensed under the MIT License. The MIT licensed code is used with
+permission and retains its original MIT license.
+
+The MIT licensed code is from the following sources:
+- [uint128_t](https://github.com/calccrypto/uint128_t)
+
## Installation and Configuration
Please refer to https://docs.qubic.org
diff --git a/src/Qubic.vcxproj b/src/Qubic.vcxproj
index 88de4c1e..d104eed9 100644
--- a/src/Qubic.vcxproj
+++ b/src/Qubic.vcxproj
@@ -23,6 +23,7 @@
+
@@ -86,6 +87,7 @@
+
diff --git a/src/Qubic.vcxproj.filters b/src/Qubic.vcxproj.filters
index 2d465caf..d686370b 100644
--- a/src/Qubic.vcxproj.filters
+++ b/src/Qubic.vcxproj.filters
@@ -237,6 +237,12 @@
network_messages
+
+ contracts
+
+
+ platform
+
diff --git a/src/contract_core/contract_def.h b/src/contract_core/contract_def.h
index 033c6cc8..f6ff0697 100644
--- a/src/contract_core/contract_def.h
+++ b/src/contract_core/contract_def.h
@@ -175,6 +175,15 @@ struct __FunctionOrProcedureBeginEndGuard
#include "contracts/MsVault.h"
// new contracts should be added above this line
+#undef CONTRACT_INDEX
+#undef CONTRACT_STATE_TYPE
+#undef CONTRACT_STATE2_TYPE
+
+#define QSWAP_CONTRACT_INDEX 12
+#define CONTRACT_INDEX QSWAP_CONTRACT_INDEX
+#define CONTRACT_STATE_TYPE QSWAP
+#define CONTRACT_STATE2_TYPE QSWAP2
+#include "contracts/Qswap.h"
#ifdef INCLUDE_CONTRACT_TEST_EXAMPLES
constexpr unsigned short TESTEXA_CONTRACT_INDEX = (CONTRACT_INDEX + 1);
@@ -250,6 +259,7 @@ constexpr struct ContractDescription
{"QVAULT", 138, 10000, sizeof(IPO)}, // proposal in epoch 136, IPO in 137, construction and first use in 138
{"MSVAULT", 149, 10000, sizeof(MSVAULT)}, // proposal in epoch 147, IPO in 148, construction and first use in 149
// new contracts should be added above this line
+ {"QSWAP", 150, 10000, sizeof(QSWAP)},
#ifdef INCLUDE_CONTRACT_TEST_EXAMPLES
{"TESTEXA", 138, 10000, sizeof(IPO)},
{"TESTEXB", 138, 10000, sizeof(IPO)},
@@ -344,6 +354,7 @@ static void initializeContracts()
REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(QVAULT);
REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(MSVAULT);
// new contracts should be added above this line
+ REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(QSWAP);
#ifdef INCLUDE_CONTRACT_TEST_EXAMPLES
REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(TESTEXA);
REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(TESTEXB);
diff --git a/src/contract_core/qpi_asset_impl.h b/src/contract_core/qpi_asset_impl.h
index 18e20c18..a6ad6d1a 100644
--- a/src/contract_core/qpi_asset_impl.h
+++ b/src/contract_core/qpi_asset_impl.h
@@ -789,3 +789,9 @@ long long QPI::QpiContextProcedureCall::transferShareOwnershipAndPossession(unsi
}
}
}
+
+bool QPI::QpiContextFunctionCall::isAssetIssued(const m256i& issuer, unsigned long long assetName) const
+{
+ bool res = ::issuanceIndex(issuer, assetName) != NO_ASSET_INDEX;
+ return res;
+}
diff --git a/src/contracts/Qswap.h b/src/contracts/Qswap.h
new file mode 100644
index 00000000..d3cf8ed3
--- /dev/null
+++ b/src/contracts/Qswap.h
@@ -0,0 +1,1751 @@
+using namespace QPI;
+
+// FIXED CONSTANTS
+constexpr uint64 QSWAP_INITIAL_MAX_POOL = 16384;
+constexpr uint64 QSWAP_MAX_POOL = QSWAP_INITIAL_MAX_POOL * X_MULTIPLIER;
+constexpr uint64 QSWAP_MAX_USER_PER_POOL = 256;
+constexpr sint64 QSWAP_MIN_LIQUDITY = 1000;
+constexpr uint32 QSWAP_SWAP_FEE_BASE = 10000;
+constexpr uint32 QSWAP_PROTOCOL_FEE_BASE = 100;
+constexpr uint32 QSWAP_POOL_CREATION_FEE_BASE = 100; // poolCreataionFee = assetIssueFee / POOL_CREATION_RATIO
+
+struct QSWAP2
+{
+};
+
+struct QSWAP : public ContractBase
+{
+public:
+ struct Fees_input{};
+ struct Fees_output{
+ uint32 assetIssuanceFee; // Amount of qus
+ uint32 poolCreationFee; // Amount of qus
+ uint32 transferFee; // Amount of qus
+ uint32 swapFee; // Number of billionths
+ uint32 protocolFee;
+ };
+
+ struct GetPoolBasicState_input{
+ id assetIssuer;
+ uint64 assetName;
+ };
+ struct GetPoolBasicState_output{
+ sint64 poolExists;
+ sint64 reservedQuAmount;
+ sint64 reservedAssetAmount;
+ sint64 totalLiqudity;
+ };
+
+ struct GetLiqudityOf_input{
+ id assetIssuer;
+ uint64 assetName;
+ id account;
+ };
+ struct GetLiqudityOf_output{
+ sint64 liqudity;
+ };
+
+ struct QuoteExactQuInput_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 quAmountIn;
+ };
+ struct QuoteExactQuInput_output{
+ sint64 assetAmountOut;
+ };
+
+ struct QuoteExactQuOutput_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 quAmountOut;
+ };
+ struct QuoteExactQuOutput_output{
+ sint64 assetAmountIn;
+ };
+
+ struct QuoteExactAssetInput_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 assetAmountIn;
+ };
+ struct QuoteExactAssetInput_output{
+ sint64 quAmountOut;
+ };
+
+ struct QuoteExactAssetOutput_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 assetAmountOut;
+ };
+ struct QuoteExactAssetOutput_output{
+ sint64 quAmountIn;
+ };
+
+ struct IssueAsset_input
+ {
+ uint64 assetName;
+ sint64 numberOfShares;
+ uint64 unitOfMeasurement;
+ sint8 numberOfDecimalPlaces;
+ };
+ struct IssueAsset_output
+ {
+ sint64 issuedNumberOfShares;
+ };
+
+ struct CreatePool_input {
+ id assetIssuer;
+ uint64 assetName;
+ };
+ struct CreatePool_output {
+ bool success;
+ };
+
+ struct TransferShareOwnershipAndPossession_input
+ {
+ id assetIssuer;
+ uint64 assetName;
+ id newOwnerAndPossessor;
+ sint64 amount;
+ };
+ struct TransferShareOwnershipAndPossession_output
+ {
+ sint64 transferredAmount;
+ };
+
+ /**
+ * @param quAmountADesired The amount of tokenA to add as liquidity if the B/A price is <= amountBDesired/amountADesired (A depreciates).
+ * @param assetAmountBDesired The amount of tokenB to add as liquidity if the A/B price is <= amountADesired/amountBDesired (B depreciates).
+ * @param quAmountMin Bounds the extent to which the B/A price can go up before the transaction reverts. Must be <= amountADesired.
+ * @param assetAmountMin Bounds the extent to which the A/B price can go up before the transaction reverts. Must be <= amountBDesired.
+ */
+ struct AddLiqudity_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 assetAmountDesired;
+ sint64 quAmountMin;
+ sint64 assetAmountMin;
+ };
+ struct AddLiqudity_output{
+ sint64 userIncreaseLiqudity;
+ sint64 quAmount;
+ sint64 assetAmount;
+ };
+
+ struct RemoveLiqudity_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 burnLiqudity;
+ sint64 quAmountMin;
+ sint64 assetAmountMin;
+ };
+
+ struct RemoveLiqudity_output {
+ sint64 quAmount;
+ sint64 assetAmount;
+ };
+
+ struct SwapExactQuForAsset_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 assetAmountOutMin;
+ };
+ struct SwapExactQuForAsset_output{
+ sint64 assetAmountOut;
+ };
+
+ struct SwapQuForExactAsset_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 assetAmountOut;
+ };
+ struct SwapQuForExactAsset_output{
+ sint64 quAmountIn;
+ };
+
+ struct SwapExactAssetForQu_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 assetAmountIn;
+ sint64 quAmountOutMin;
+ };
+ struct SwapExactAssetForQu_output{
+ sint64 quAmountOut;
+ };
+
+ struct SwapAssetForExactQu_input{
+ id assetIssuer;
+ uint64 assetName;
+ sint64 assetAmountInMax;
+ sint64 quAmountOut;
+ };
+ struct SwapAssetForExactQu_output{
+ sint64 assetAmountIn;
+ };
+
+protected:
+ uint32 swapFeeRate; // e.g. 30: 0.3% (base: 10_000)
+ uint32 protocolFeeRate; // e.g. 20: 20% (base: 100) only charge in qu
+ uint32 poolCreationRate; // e.g. 10: 10% (base: 100)
+
+ uint64 protocolEarnedFee;
+ uint64 distributedAmount;
+
+ struct PoolBasicState{
+ id poolID;
+ sint64 reservedQuAmount;
+ sint64 reservedAssetAmount;
+ sint64 totalLiqudity;
+ };
+
+ struct LiqudityInfo {
+ id entity;
+ sint64 liqudity;
+ };
+
+ Array mPoolBasicStates;
+ Collection mLiquditys;
+
+ inline static sint64 min(sint64 a, sint64 b) {
+ return (a < b) ? a : b;
+ }
+
+ // find the sqrt of a*b
+ inline static sint64 sqrt(sint64& a, sint64& b, uint128& prod, uint128& y, uint128& z) {
+ if (a == b) { return a; }
+
+ prod = uint128(a) * uint128(b);
+
+ // (prod + 1) / 2;
+ z = div(prod+uint128(1), uint128(2));
+ y = prod;
+
+ while(z < y){
+ y = z;
+ // (prod / z + z) / 2;
+ z = div((div(prod, z) + z), uint128(2));
+ }
+
+ return sint64(y.low);
+ }
+
+ inline static sint64 quoteEquivalentAmountB(sint64& amountADesired, sint64& reserveA, sint64& reserveB, uint128& tmpRes) {
+ // amountDesired * reserveB / reserveA
+ tmpRes = div(uint128(amountADesired) * uint128(reserveB), uint128(reserveA));
+
+ if ((tmpRes.high != 0)|| (tmpRes.low > 0x7FFFFFFFFFFFFFFF)) {
+ return -1;
+ } else {
+ return sint64(tmpRes.low);
+ }
+ }
+
+ // reserveIn * reserveOut = (reserveIn + amountIn * (1-fee)) * (reserveOut - x)
+ // x = reserveOut * amountIn * (1-fee) / (reserveIn + amountIn * (1-fee))
+ inline static sint64 getAmountOutTakeFeeFromInToken(
+ sint64& amountIn,
+ sint64& reserveIn,
+ sint64& reserveOut,
+ uint32 fee,
+ uint128& amountInWithFee,
+ uint128& numerator,
+ uint128& denominator,
+ uint128& tmpRes
+ ) {
+ amountInWithFee = uint128(amountIn) * uint128(QSWAP_SWAP_FEE_BASE - fee);
+ numerator = uint128(reserveOut) * amountInWithFee;
+ denominator = uint128(reserveIn) * uint128(QSWAP_SWAP_FEE_BASE) + amountInWithFee;
+
+ // numerator / denominator
+ tmpRes = div(numerator, denominator);
+ if ((tmpRes.high != 0) || (tmpRes.low > 0x7FFFFFFFFFFFFFFF)) {
+ return -1;
+ } else {
+ return sint64(tmpRes.low);
+ }
+ }
+
+ // reserveIn * reserveOut = (reserveIn + x * (1-fee)) * (reserveOut - amountOut)
+ // x = (reserveIn * amountOut)/((1-fee) * (reserveOut - amountOut)
+ inline static sint64 getAmountInTakeFeeFromInToken(sint64& amountOut, sint64& reserveIn, sint64& reserveOut, uint32 fee, uint128& tmpRes) {
+ // reserveIn*amountOut/(reserveOut - amountOut)*QSWAP_SWAP_FEE_BASE / (QSWAP_SWAP_FEE_BASE - fee)
+ tmpRes = div(
+ div(
+ uint128(reserveIn) * uint128(amountOut),
+ uint128(reserveOut - amountOut)
+ ) * uint128(QSWAP_SWAP_FEE_BASE),
+ uint128(QSWAP_SWAP_FEE_BASE - fee)
+ );
+ if ((tmpRes.high != 0) || (tmpRes.low > 0x7FFFFFFFFFFFFFFF)) {
+ return -1;
+ } else {
+ return sint64(tmpRes.low);
+ }
+ }
+
+ // (reserveIn + amountIn) * (reserveOut - x) = reserveIn * reserveOut
+ // x = reserveOut * amountIn / (reserveIn + amountIn)
+ inline static sint64 getAmountOutTakeFeeFromOutToken(sint64& amountIn, sint64& reserveIn, sint64& reserveOut, uint32 fee, uint128& numerator, uint128& denominator, uint128& tmpRes) {
+ numerator = uint128(reserveOut) * uint128(amountIn);
+ denominator = uint128(reserveIn + amountIn);
+
+ tmpRes = div(numerator, denominator);
+ if ((tmpRes.high != 0)|| (tmpRes.low > 0x7FFFFFFFFFFFFFFF)) {
+ return -1;
+ } else {
+ return sint64(tmpRes.low);
+ }
+ }
+
+ // (reserveIn + x) * (reserveOut - amountOut/(1 - fee)) = reserveIn * reserveOut
+ // x = (reserveIn * amountOut ) / (reserveOut * (1-fee) - amountOut)
+ inline static sint64 getAmountInTakeFeeFromOutToken(sint64& amountOut, sint64& reserveIn, sint64& reserveOut, uint32 fee, uint128& numerator, uint128& denominator, uint128& tmpRes) {
+ numerator = uint128(reserveIn) * uint128(amountOut);
+ if (uint128(reserveOut) * uint128(QSWAP_SWAP_FEE_BASE - fee) / uint128(QSWAP_SWAP_FEE_BASE) < uint128(amountOut)){
+ return -1;
+ }
+ denominator = uint128(reserveOut) * uint128(QSWAP_SWAP_FEE_BASE - fee) / uint128(QSWAP_SWAP_FEE_BASE) - uint128(amountOut);
+
+ tmpRes = div(numerator, denominator);
+ if ((tmpRes.high != 0)|| (tmpRes.low > 0x7FFFFFFFFFFFFFFF)) {
+ return -1;
+ } else {
+ return sint64(tmpRes.low);
+ }
+ }
+
+ struct Fees_locals{
+ QX::Fees_input feesInput;
+ QX::Fees_output feesOutput;
+ };
+
+ PUBLIC_FUNCTION_WITH_LOCALS(Fees)
+ CALL_OTHER_CONTRACT_FUNCTION(QX, Fees, locals.feesInput, locals.feesOutput);
+
+ output.assetIssuanceFee = locals.feesOutput.assetIssuanceFee;
+ output.poolCreationFee = uint32(div(uint64(locals.feesOutput.assetIssuanceFee) * uint64(state.poolCreationRate), uint64(QSWAP_POOL_CREATION_FEE_BASE)));
+ output.transferFee = locals.feesOutput.transferFee;
+ output.swapFee = state.swapFeeRate;
+ output.protocolFee = state.protocolFeeRate;
+ _
+
+ struct GetPoolBasicState_locals{
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+ uint32 i0;
+ };
+
+ PUBLIC_FUNCTION_WITH_LOCALS(GetPoolBasicState)
+ output.poolExists = 0;
+ output.totalLiqudity = -1;
+ output.reservedAssetAmount = -1;
+ output.reservedQuAmount = -1;
+
+ // asset not issued
+ if (!qpi.isAssetIssued(input.assetIssuer, input.assetName)){
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.poolSlot = NULL_INDEX;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0++){
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ if (locals.poolSlot == NULL_INDEX){
+ return;
+ }
+
+ output.poolExists = 1;
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ output.reservedQuAmount = locals.poolBasicState.reservedQuAmount;
+ output.reservedAssetAmount = locals.poolBasicState.reservedAssetAmount;
+ output.totalLiqudity = locals.poolBasicState.totalLiqudity;
+ _
+
+ struct GetLiqudityOf_locals{
+ id poolID;
+ sint64 liqElementIndex;
+ };
+
+ PUBLIC_FUNCTION_WITH_LOCALS(GetLiqudityOf)
+ output.liqudity = 0;
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.liqElementIndex = state.mLiquditys.headIndex(locals.poolID, 0);
+
+ while (locals.liqElementIndex != NULL_INDEX) {
+ if (state.mLiquditys.element(locals.liqElementIndex).entity == input.account) {
+ output.liqudity = state.mLiquditys.element(locals.liqElementIndex).liqudity;
+ return;
+ }
+ locals.liqElementIndex = state.mLiquditys.nextElementIndex(locals.liqElementIndex);
+ }
+ _
+
+ struct QuoteExactQuInput_locals{
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+
+ uint32 i0;
+ uint128 i1, i2, i3, i4;
+ };
+
+ PUBLIC_FUNCTION_WITH_LOCALS(QuoteExactQuInput)
+ output.assetAmountOut = -1;
+
+ if (input.quAmountIn <= 0) {
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.poolSlot = -1;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ // no available solt for new pool
+ if (locals.poolSlot == -1) {
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ // no liqudity in the pool
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ return;
+ }
+
+ output.assetAmountOut = getAmountOutTakeFeeFromInToken(
+ input.quAmountIn,
+ locals.poolBasicState.reservedQuAmount,
+ locals.poolBasicState.reservedAssetAmount,
+ state.swapFeeRate,
+ locals.i1,
+ locals.i2,
+ locals.i3,
+ locals.i4
+ );
+ _
+
+ struct QuoteExactQuOutput_locals{
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+
+ uint32 i0;
+ uint128 i1, i2, i3;
+ };
+
+ PUBLIC_FUNCTION_WITH_LOCALS(QuoteExactQuOutput)
+ output.assetAmountIn = -1;
+
+ if (input.quAmountOut <= 0) {
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.poolSlot = -1;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ // no available solt for new pool
+ if (locals.poolSlot == -1) {
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ // no liqudity in the pool
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ return;
+ }
+
+ if (input.quAmountOut >= locals.poolBasicState.reservedQuAmount){
+ return;
+ }
+
+ output.assetAmountIn = getAmountInTakeFeeFromOutToken(
+ input.quAmountOut,
+ locals.poolBasicState.reservedAssetAmount,
+ locals.poolBasicState.reservedQuAmount,
+ state.swapFeeRate,
+ locals.i1,
+ locals.i2,
+ locals.i3
+ );
+ _
+
+ struct QuoteExactAssetInput_locals{
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+ sint64 quAmountOutWithFee;
+
+ uint32 i0;
+ uint128 i1, i2, i3;
+ };
+
+ PUBLIC_FUNCTION_WITH_LOCALS(QuoteExactAssetInput)
+ output.quAmountOut = -1;
+
+ if (input.assetAmountIn <= 0) {
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.poolSlot = -1;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ // no available solt for new pool
+ if (locals.poolSlot == -1) {
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ // no liqudity in the pool
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ return;
+ }
+
+ locals.quAmountOutWithFee = getAmountOutTakeFeeFromOutToken(
+ input.assetAmountIn,
+ locals.poolBasicState.reservedAssetAmount,
+ locals.poolBasicState.reservedQuAmount,
+ state.swapFeeRate,
+ locals.i1,
+ locals.i2,
+ locals.i3
+ );
+
+ // above call overflow
+ if (locals.quAmountOutWithFee == -1){
+ return;
+ }
+
+ // amount * (1-fee), no overflow risk
+ output.quAmountOut = sint64((
+ uint128(locals.quAmountOutWithFee) *
+ uint128(QSWAP_SWAP_FEE_BASE - state.swapFeeRate) /
+ uint128(QSWAP_SWAP_FEE_BASE)
+ ).low);
+ _
+
+ struct QuoteExactAssetOutput_locals{
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+
+ uint32 i0;
+ uint128 i1;
+ };
+
+ PUBLIC_FUNCTION_WITH_LOCALS(QuoteExactAssetOutput)
+ output.quAmountIn = -1;
+
+ if (input.assetAmountOut <= 0) {
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.poolSlot = -1;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ // no available solt for new pool
+ if (locals.poolSlot == -1) {
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ // no liqudity in the pool
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ return;
+ }
+
+ if (input.assetAmountOut >= locals.poolBasicState.reservedAssetAmount) {
+ return;
+ }
+
+ output.quAmountIn = getAmountInTakeFeeFromInToken(
+ input.assetAmountOut,
+ locals.poolBasicState.reservedQuAmount,
+ locals.poolBasicState.reservedAssetAmount,
+ state.swapFeeRate,
+ locals.i1
+ );
+ _
+
+//
+// procedure
+//
+ struct IssueAsset_locals{
+ QX::Fees_input feesInput;
+ QX::Fees_output feesOutput;
+ };
+
+ PUBLIC_PROCEDURE_WITH_LOCALS(IssueAsset)
+ CALL_OTHER_CONTRACT_FUNCTION(QX, Fees, locals.feesInput, locals.feesOutput);
+
+ output.issuedNumberOfShares = 0;
+ if ((qpi.invocationReward() < locals.feesOutput.assetIssuanceFee)) {
+ if (qpi.invocationReward() > 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ }
+ return;
+ }
+
+ // check the validity of input
+ if ((input.numberOfShares <= 0) || (input.numberOfDecimalPlaces < 0)){
+ if (qpi.invocationReward() > 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ }
+ return;
+ }
+
+ // asset already issued
+ if (qpi.isAssetIssued(qpi.invocator(), input.assetName)) {
+ if (qpi.invocationReward() > 0 ) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ }
+ return;
+ }
+
+ output.issuedNumberOfShares = qpi.issueAsset(
+ input.assetName,
+ qpi.invocator(),
+ input.numberOfDecimalPlaces,
+ input.numberOfShares,
+ input.unitOfMeasurement
+ );
+
+ if (output.issuedNumberOfShares == 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ } else if (qpi.invocationReward() > locals.feesOutput.assetIssuanceFee ){
+ qpi.transfer(qpi.invocator(), qpi.invocationReward() - locals.feesOutput.assetIssuanceFee);
+ state.protocolEarnedFee += locals.feesOutput.assetIssuanceFee;
+ }
+ _
+
+ struct CreatePool_locals{
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+ QX::Fees_input feesInput;
+ QX::Fees_output feesOutput;
+ uint32 poolCreationFee;
+
+ uint32 i0, i1;
+ };
+
+ // create uniswap like pool
+ // TODO: reject if there is no shares avaliabe shares in current contract, e.g. asset is issue in contract qx
+ PUBLIC_PROCEDURE_WITH_LOCALS(CreatePool)
+ output.success = false;
+
+ CALL_OTHER_CONTRACT_FUNCTION(QX, Fees, locals.feesInput, locals.feesOutput);
+ locals.poolCreationFee = uint32(div(uint64(locals.feesOutput.assetIssuanceFee) * uint64(state.poolCreationRate), uint64(QSWAP_POOL_CREATION_FEE_BASE)));
+
+ // fee check
+ if(qpi.invocationReward() < locals.poolCreationFee ) {
+ if(qpi.invocationReward() > 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ }
+ return;
+ }
+
+ // asset no exist
+ if (!qpi.isAssetIssued(qpi.invocator(), input.assetName)) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ // check if pool already exist
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL ; locals.i0 ++ ){
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+ }
+
+ // find an vacant pool slot
+ locals.poolSlot = -1;
+ for (locals.i1 = 0; locals.i1 < QSWAP_MAX_POOL; locals.i1 ++) {
+ if (state.mPoolBasicStates.get(locals.i1).poolID == id(0,0,0,0)) {
+ locals.poolSlot = locals.i1;
+ break;
+ }
+ }
+
+ // no available solt for new pool
+ if (locals.poolSlot == -1) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.poolBasicState.poolID = locals.poolID;
+ locals.poolBasicState.reservedAssetAmount = 0;
+ locals.poolBasicState.reservedQuAmount = 0;
+ locals.poolBasicState.totalLiqudity = 0;
+
+ state.mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
+
+ if(qpi.invocationReward() > locals.poolCreationFee) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward() - locals.poolCreationFee );
+ }
+ state.protocolEarnedFee += locals.poolCreationFee;
+
+ output.success = true;
+ _
+
+
+ struct AddLiqudity_locals
+ {
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+ LiqudityInfo tmpLiqudity;
+
+ sint64 userLiqudityElementIndex;
+ sint64 quAmountDesired;
+
+ sint64 quTransferAmount;
+ sint64 assetTransferAmount;
+ sint64 quOptimalAmount;
+ sint64 assetOptimalAmount;
+ sint64 increaseLiqudity;
+ sint64 reservedAssetAmountBefore;
+ sint64 reservedAssetAmountAfter;
+
+ uint128 tmpIncLiq0;
+ uint128 tmpIncLiq1;
+
+ uint32 i0;
+ uint128 i1, i2, i3;
+ };
+
+ PUBLIC_PROCEDURE_WITH_LOCALS(AddLiqudity)
+ output.userIncreaseLiqudity = 0;
+ output.assetAmount = 0;
+ output.quAmount = 0;
+
+ // add liqudity must stake both qu and asset
+ if (qpi.invocationReward() <= 0) {
+ return;
+ }
+
+ locals.quAmountDesired = qpi.invocationReward();
+
+ // check the vadility of input params
+ if ((input.assetAmountDesired <= 0) ||
+ (input.quAmountMin < 0) ||
+ (input.assetAmountMin < 0)
+ ){
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ // check the pool existance
+ locals.poolSlot = -1;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ if (locals.poolSlot == -1) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ // check if pool state meet the input condition before desposit
+ // and confirm the final qu and asset amount to stake
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ locals.quTransferAmount = locals.quAmountDesired;
+ locals.assetTransferAmount = input.assetAmountDesired;
+ } else {
+ locals.assetOptimalAmount = quoteEquivalentAmountB(
+ locals.quAmountDesired,
+ locals.poolBasicState.reservedQuAmount,
+ locals.poolBasicState.reservedAssetAmount,
+ locals.i1
+ );
+ // overflow
+ if (locals.assetOptimalAmount == -1) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return ;
+ }
+
+ if (locals.assetOptimalAmount <= input.assetAmountDesired ){
+ if (locals.assetOptimalAmount < input.assetAmountMin) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return ;
+ }
+ locals.quTransferAmount = locals.quAmountDesired;
+ locals.assetTransferAmount = locals.assetOptimalAmount;
+ } else {
+ locals.quOptimalAmount = quoteEquivalentAmountB(
+ input.assetAmountDesired,
+ locals.poolBasicState.reservedAssetAmount,
+ locals.poolBasicState.reservedQuAmount,
+ locals.i1
+ );
+ // overflow
+ if (locals.quOptimalAmount == -1) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return ;
+ }
+ if (locals.quOptimalAmount > locals.quAmountDesired) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return ;
+ }
+ if (locals.quOptimalAmount < input.quAmountMin){
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return ;
+ }
+ locals.quTransferAmount = locals.quOptimalAmount;
+ locals.assetTransferAmount = input.assetAmountDesired;
+ }
+ }
+
+ // check if the qu is enough
+ if (qpi.invocationReward() < locals.quTransferAmount){
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // check if the asset is enough
+ if (qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ qpi.invocator(),
+ qpi.invocator(),
+ SELF_INDEX,
+ SELF_INDEX
+ ) < locals.assetTransferAmount)
+ {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // for pool's initial mint
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ locals.increaseLiqudity = sqrt(locals.quTransferAmount, locals.assetTransferAmount, locals.i1, locals.i2, locals.i3);
+
+ if (locals.increaseLiqudity < QSWAP_MIN_LIQUDITY ){
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.reservedAssetAmountBefore = qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ SELF_INDEX,
+ SELF_INDEX
+ );
+ qpi.transferShareOwnershipAndPossession(
+ input.assetName,
+ input.assetIssuer,
+ qpi.invocator(),
+ qpi.invocator(),
+ locals.assetTransferAmount,
+ SELF
+ );
+ locals.reservedAssetAmountAfter = qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ SELF_INDEX,
+ SELF_INDEX
+ );
+
+ if (locals.reservedAssetAmountAfter - locals.reservedAssetAmountBefore < locals.assetTransferAmount) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // permanently lock the first MINIMUM_LIQUIDITY tokens
+ locals.tmpLiqudity.entity = SELF;
+ locals.tmpLiqudity.liqudity = QSWAP_MIN_LIQUDITY;
+ state.mLiquditys.add(locals.poolID, locals.tmpLiqudity, 0);
+
+ locals.tmpLiqudity.entity = qpi.invocator();
+ locals.tmpLiqudity.liqudity = locals.increaseLiqudity - QSWAP_MIN_LIQUDITY;
+ state.mLiquditys.add(locals.poolID, locals.tmpLiqudity, 0);
+
+ output.quAmount = locals.quTransferAmount;
+ output.assetAmount = locals.assetTransferAmount;
+ output.userIncreaseLiqudity = locals.increaseLiqudity - QSWAP_MIN_LIQUDITY;
+ } else {
+ locals.tmpIncLiq0 = div(
+ uint128(locals.quTransferAmount) * uint128(locals.poolBasicState.totalLiqudity),
+ uint128(locals.poolBasicState.reservedQuAmount)
+ );
+ if (locals.tmpIncLiq0.high != 0 || locals.tmpIncLiq0.low > 0x7FFFFFFFFFFFFFFF) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+ locals.tmpIncLiq1 = div(
+ uint128(locals.assetTransferAmount) * uint128(locals.poolBasicState.totalLiqudity),
+ uint128(locals.poolBasicState.reservedAssetAmount)
+ );
+ if (locals.tmpIncLiq1.high != 0 || locals.tmpIncLiq1.low > 0x7FFFFFFFFFFFFFFF) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // increaseLiquity = min(
+ // quTransferAmount * totalLiquity / reserveQuAmount,
+ // assetTransferAmount * totalLiquity / reserveAssetAmount
+ // );
+ locals.increaseLiqudity = min(sint64(locals.tmpIncLiq0.low), sint64(locals.tmpIncLiq1.low));
+
+ // maybe too little input
+ if (locals.increaseLiqudity == 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // find user liqudity index
+ locals.userLiqudityElementIndex = state.mLiquditys.headIndex(locals.poolID, 0);
+ while (locals.userLiqudityElementIndex != NULL_INDEX) {
+ if(state.mLiquditys.element(locals.userLiqudityElementIndex).entity == qpi.invocator()) {
+ break;
+ }
+
+ locals.userLiqudityElementIndex = state.mLiquditys.nextElementIndex(locals.userLiqudityElementIndex);
+ }
+
+ // no more space for new liqudity item
+ if ((locals.userLiqudityElementIndex == NULL_INDEX) && ( state.mLiquditys.population() == state.mLiquditys.capacity())) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // transfer the asset from invocator to contract
+ locals.reservedAssetAmountBefore = qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ SELF_INDEX,
+ SELF_INDEX
+ );
+ qpi.transferShareOwnershipAndPossession(
+ input.assetName,
+ input.assetIssuer,
+ qpi.invocator(),
+ qpi.invocator(),
+ locals.assetTransferAmount,
+ SELF
+ );
+ locals.reservedAssetAmountAfter = qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ SELF_INDEX,
+ SELF_INDEX
+ );
+
+ // only trust the amount in the contract
+ if (locals.reservedAssetAmountAfter - locals.reservedAssetAmountBefore < locals.assetTransferAmount) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ if (locals.userLiqudityElementIndex == NULL_INDEX) {
+ locals.tmpLiqudity.entity = qpi.invocator();
+ locals.tmpLiqudity.liqudity = locals.increaseLiqudity;
+ state.mLiquditys.add(locals.poolID, locals.tmpLiqudity, 0);
+ } else {
+ locals.tmpLiqudity = state.mLiquditys.element(locals.userLiqudityElementIndex);
+ locals.tmpLiqudity.liqudity += locals.increaseLiqudity;
+ state.mLiquditys.replace(locals.userLiqudityElementIndex, locals.tmpLiqudity);
+ }
+
+ output.quAmount = locals.quTransferAmount;
+ output.assetAmount = locals.assetTransferAmount;
+ output.userIncreaseLiqudity = locals.increaseLiqudity;
+ }
+
+ locals.poolBasicState.reservedQuAmount += locals.quTransferAmount;
+ locals.poolBasicState.reservedAssetAmount += locals.assetTransferAmount;
+ locals.poolBasicState.totalLiqudity += locals.increaseLiqudity;
+
+ state.mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
+
+ if (qpi.invocationReward() > locals.quTransferAmount) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward() - locals.quTransferAmount);
+ }
+ _
+
+ struct RemoveLiqudity_locals {
+ id poolID;
+ PoolBasicState poolBasicState;
+ sint64 userLiqudityElementIndex;
+ sint64 poolSlot;
+ LiqudityInfo userLiqudity;
+ sint64 burnQuAmount;
+ sint64 burnAssetAmount;
+
+ uint32 i0;
+ };
+
+ PUBLIC_PROCEDURE_WITH_LOCALS(RemoveLiqudity)
+ output.quAmount = 0;
+ output.assetAmount = 0;
+
+ if (qpi.invocationReward() > 0 ) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ }
+
+ // check the vadility of input params
+ if (input.quAmountMin < 0 || input.assetAmountMin < 0) {
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ // get the pool's basic state
+ locals.poolSlot = -1;
+
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ // the pool does not exsit
+ if (locals.poolSlot == -1) {
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ locals.userLiqudityElementIndex = state.mLiquditys.headIndex(locals.poolID, 0);
+ while (locals.userLiqudityElementIndex != NULL_INDEX) {
+ if(state.mLiquditys.element(locals.userLiqudityElementIndex).entity == qpi.invocator()) {
+ break;
+ }
+
+ locals.userLiqudityElementIndex = state.mLiquditys.nextElementIndex(locals.userLiqudityElementIndex);
+ }
+
+ if (locals.userLiqudityElementIndex == NULL_INDEX){
+ return;
+ }
+
+ locals.userLiqudity = state.mLiquditys.element(locals.userLiqudityElementIndex);
+
+ // not enough liqudity for burning
+ if (locals.userLiqudity.liqudity < input.burnLiqudity ){
+ return;
+ }
+
+ if (locals.poolBasicState.totalLiqudity < input.burnLiqudity ){
+ return;
+ }
+
+ // since burnLiqudity < totalLiqudity, so there will be no overflow risk
+ locals.burnQuAmount = sint64(div(
+ uint128(input.burnLiqudity) * uint128(locals.poolBasicState.reservedQuAmount),
+ uint128(locals.poolBasicState.totalLiqudity)
+ ).low);
+
+ // since burnLiqudity < totalLiqudity, so there will be no overflow risk
+ locals.burnAssetAmount = sint64(div(
+ uint128(input.burnLiqudity) * uint128(locals.poolBasicState.reservedAssetAmount),
+ uint128(locals.poolBasicState.totalLiqudity)
+ ).low);
+
+
+ if ((locals.burnQuAmount < input.quAmountMin) || (locals.burnAssetAmount < input.assetAmountMin)){
+ return;
+ }
+
+ // return qu and asset to invocator
+ qpi.transfer(qpi.invocator(), locals.burnQuAmount);
+ qpi.transferShareOwnershipAndPossession(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ locals.burnAssetAmount,
+ qpi.invocator()
+ );
+
+ output.quAmount = locals.burnQuAmount;
+ output.assetAmount = locals.burnAssetAmount;
+
+ // modify invocator's liqudity info
+ locals.userLiqudity.liqudity -= input.burnLiqudity;
+ if (locals.userLiqudity.liqudity == 0) {
+ state.mLiquditys.remove(locals.userLiqudityElementIndex);
+ } else {
+ state.mLiquditys.replace(locals.userLiqudityElementIndex, locals.userLiqudity);
+ }
+
+ // modify the pool's liqudity info
+ locals.poolBasicState.totalLiqudity -= input.burnLiqudity;
+ locals.poolBasicState.reservedQuAmount -= locals.burnQuAmount;
+ locals.poolBasicState.reservedAssetAmount -= locals.burnAssetAmount;
+
+ state.mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
+ _
+
+ struct SwapExactQuForAsset_locals{
+ id poolID;
+ sint64 poolSlot;
+ sint64 quAmountIn;
+ PoolBasicState poolBasicState;
+ sint64 feeAmount;
+ sint64 assetAmountOut;
+ sint64 feeToProtocol;
+
+ uint32 i0;
+ uint128 i1, i2, i3, i4;
+ };
+
+ // given an input qu amountIn, only execute swap in case (amountOut >= amountOutMin)
+ // https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02#swapexacttokensfortokens
+ PUBLIC_PROCEDURE_WITH_LOCALS(SwapExactQuForAsset)
+ output.assetAmountOut = 0;
+
+ // require input qu > 0
+ if (qpi.invocationReward() <= 0) {
+ return;
+ }
+
+ if (input.assetAmountOutMin < 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.quAmountIn = qpi.invocationReward();
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.poolSlot = -1;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ if (locals.poolSlot == -1) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ // check the liqudity validity
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.assetAmountOut = getAmountOutTakeFeeFromInToken(
+ locals.quAmountIn,
+ locals.poolBasicState.reservedQuAmount,
+ locals.poolBasicState.reservedAssetAmount,
+ state.swapFeeRate,
+ locals.i1,
+ locals.i2,
+ locals.i3,
+ locals.i4
+ );
+
+ // overflow
+ if (locals.assetAmountOut == -1){
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // not meet user's amountOut requirement
+ if (locals.assetAmountOut < input.assetAmountOutMin) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // transfer the asset from pool to qpi.invocator()
+ output.assetAmountOut = qpi.transferShareOwnershipAndPossession(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ locals.assetAmountOut,
+ qpi.invocator()
+ ) < 0 ? 0: locals.assetAmountOut;
+
+ // in case asset transfer failed
+ if (output.assetAmountOut == 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // take fee from qu
+ // quAmountIn * swapFeeRate / QSWAP_SWAP_FEE_BASE * state.protocolFeeRate / QSWAP_PROTOCOL_FEE_BASE
+ // no overflow risk
+ locals.feeToProtocol = sint64(div(
+ div(
+ uint128(locals.quAmountIn) * uint128(state.swapFeeRate),
+ uint128(QSWAP_SWAP_FEE_BASE)
+ ) * uint128(state.protocolFeeRate),
+ uint128(QSWAP_PROTOCOL_FEE_BASE)
+ ).low);
+ state.protocolEarnedFee += locals.feeToProtocol;
+
+ locals.poolBasicState.reservedQuAmount += locals.quAmountIn - locals.feeToProtocol;
+ locals.poolBasicState.reservedAssetAmount -= locals.assetAmountOut;
+ state.mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
+ _
+
+ struct SwapQuForExactAsset_locals{
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+ sint64 quAmountIn;
+ sint64 feeToProtocol;
+ sint64 transferredAssetAmount;
+
+ uint32 i0;
+ uint128 i1;
+ };
+
+ // https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02#swaptokensforexacttokens
+ PUBLIC_PROCEDURE_WITH_LOCALS(SwapQuForExactAsset)
+ output.quAmountIn = 0;
+
+ // require input qu amount > 0
+ if (qpi.invocationReward() <= 0) {
+ return;
+ }
+
+ // check input param validity
+ if (input.assetAmountOut <= 0){
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.poolSlot = -1;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ if (locals.poolSlot == -1) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ // check if there is liqudity in the poool
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // check if reserved asset is enough
+ if (input.assetAmountOut >= locals.poolBasicState.reservedAssetAmount) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.quAmountIn = getAmountInTakeFeeFromInToken(
+ input.assetAmountOut,
+ locals.poolBasicState.reservedQuAmount,
+ locals.poolBasicState.reservedAssetAmount,
+ state.swapFeeRate,
+ locals.i1
+ );
+
+ // above call overflow
+ if (locals.quAmountIn == -1) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // not enough qu amountIn
+ if (locals.quAmountIn > qpi.invocationReward()) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // not meet user's amountIn limit
+ if (locals.quAmountIn > qpi.invocationReward()){
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ // transfer the asset from pool to qpi.invocator()
+ locals.transferredAssetAmount = qpi.transferShareOwnershipAndPossession(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ input.assetAmountOut,
+ qpi.invocator()
+ ) < 0 ? 0: input.assetAmountOut;
+
+ // asset transfer failed
+ if (locals.transferredAssetAmount == 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ output.quAmountIn = locals.quAmountIn;
+ if (qpi.invocationReward() > locals.quAmountIn) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward() - locals.quAmountIn);
+ }
+
+ // update pool states
+ // no overflow risk
+ // locals.quAmountIn * state.swapFeeRate / QSWAP_SWAP_FEE_BASE * state.protocolFeeRate / QSWAP_PROTOCOL_FEE_BASE
+ locals.feeToProtocol = sint64(div(
+ div(
+ uint128(locals.quAmountIn) * uint128(state.swapFeeRate),
+ uint128(QSWAP_SWAP_FEE_BASE)
+ ) * uint128(state.protocolFeeRate),
+ uint128(QSWAP_PROTOCOL_FEE_BASE)
+ ).low);
+
+ state.protocolEarnedFee += locals.feeToProtocol;
+ locals.poolBasicState.reservedQuAmount += locals.quAmountIn - locals.feeToProtocol;
+ locals.poolBasicState.reservedAssetAmount -= input.assetAmountOut;
+
+ state.mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
+ _
+
+ struct SwapExactAssetForQu_locals{
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+ sint64 quAmountOut;
+ sint64 quAmountOutWithFee;
+ sint64 protocolFee;
+ sint64 transferredAssetAmountBefore;
+ sint64 transferredAssetAmountAfter;
+
+ uint32 i0;
+ uint128 i1, i2, i3;
+ };
+
+ // given an amount of asset swap in, only execute swaping if quAmountOut >= input.amountOutMin
+ PUBLIC_PROCEDURE_WITH_LOCALS(SwapExactAssetForQu)
+ output.quAmountOut = 0;
+ if (qpi.invocationReward() > 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ }
+
+ // check input param validity
+ if ((input.assetAmountIn <= 0 )||(input.quAmountOutMin < 0)){
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.poolSlot = -1;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ if (locals.poolSlot == -1) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ // check the liqudity validity
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ return;
+ }
+
+ // invocator's asset not enough
+ if (qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ qpi.invocator(),
+ qpi.invocator(),
+ SELF_INDEX,
+ SELF_INDEX
+ ) < input.assetAmountIn ) {
+ return;
+ }
+
+ locals.quAmountOutWithFee = getAmountOutTakeFeeFromOutToken(
+ input.assetAmountIn,
+ locals.poolBasicState.reservedAssetAmount,
+ locals.poolBasicState.reservedQuAmount,
+ state.swapFeeRate,
+ locals.i1,
+ locals.i2,
+ locals.i3
+ );
+
+ // above call overflow
+ if (locals.quAmountOutWithFee == -1){
+ return;
+ }
+
+ // no overflow risk
+ // locals.quAmountOutWithFee * (QSWAP_SWAP_FEE_BASE - state.swapFeeRate) / QSWAP_SWAP_FEE_BASE
+ locals.quAmountOut = sint64(div(
+ uint128(locals.quAmountOutWithFee) * uint128(QSWAP_SWAP_FEE_BASE - state.swapFeeRate),
+ uint128(QSWAP_SWAP_FEE_BASE)
+ ).low);
+
+ // no overflow risk
+ // locals.quAmountOutWithFee * state.swapFeeRate / QSWAP_SWAP_FEE_BASE * state.protocolFeeRate / QSWAP_PROTOCOL_FEE_BASE
+ locals.protocolFee = sint64(div(
+ div(
+ uint128(locals.quAmountOutWithFee) * uint128(state.swapFeeRate),
+ uint128(QSWAP_SWAP_FEE_BASE)
+ ) * uint128(state.protocolFeeRate),
+ uint128(QSWAP_PROTOCOL_FEE_BASE)
+ ).low
+ );
+
+ // not meet user min amountOut requirement
+ if (locals.quAmountOut < input.quAmountOutMin) {
+ return;
+ }
+
+ locals.transferredAssetAmountBefore = qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ SELF_INDEX,
+ SELF_INDEX
+ );
+ qpi.transferShareOwnershipAndPossession(
+ input.assetName,
+ input.assetIssuer,
+ qpi.invocator(),
+ qpi.invocator(),
+ input.assetAmountIn,
+ SELF
+ );
+ locals.transferredAssetAmountAfter = qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ SELF_INDEX,
+ SELF_INDEX
+ );
+
+ // pool does not receive enough asset
+ if (locals.transferredAssetAmountAfter - locals.transferredAssetAmountBefore < input.assetAmountIn) {
+ return;
+ }
+
+ qpi.transfer(qpi.invocator(), locals.quAmountOut);
+ output.quAmountOut = locals.quAmountOut;
+
+ // update pool states
+ locals.poolBasicState.reservedAssetAmount += input.assetAmountIn;
+ locals.poolBasicState.reservedQuAmount -= locals.quAmountOut;
+ locals.poolBasicState.reservedQuAmount -= locals.protocolFee;
+
+ state.protocolEarnedFee += locals.protocolFee;
+
+ state.mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
+ _
+
+ struct SwapAssetForExactQu_locals{
+ id poolID;
+ sint64 poolSlot;
+ PoolBasicState poolBasicState;
+ sint64 assetAmountIn;
+ sint64 protocolFee;
+ sint64 transferredAssetAmountBefore;
+ sint64 transferredAssetAmountAfter;
+
+ uint32 i0;
+ uint128 i1, i2, i3;
+ };
+
+ PUBLIC_PROCEDURE_WITH_LOCALS(SwapAssetForExactQu)
+ output.assetAmountIn = 0;
+ if (qpi.invocationReward() > 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ }
+
+ if ((input.assetAmountInMax <= 0 )||(input.quAmountOut <= 0)){
+ return;
+ }
+
+ locals.poolID = input.assetIssuer;
+ locals.poolID.u64._3 = input.assetName;
+
+ locals.poolSlot = -1;
+ for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++) {
+ if (state.mPoolBasicStates.get(locals.i0).poolID == locals.poolID) {
+ locals.poolSlot = locals.i0;
+ break;
+ }
+ }
+
+ if (locals.poolSlot == -1) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ locals.poolBasicState = state.mPoolBasicStates.get(locals.poolSlot);
+
+ // check the liqudity validity
+ if (locals.poolBasicState.totalLiqudity == 0) {
+ return;
+ }
+
+ // pool does not hold enough asset
+ if (input.quAmountOut >= locals.poolBasicState.reservedQuAmount) {
+ return;
+ }
+
+ locals.assetAmountIn = getAmountInTakeFeeFromOutToken(
+ input.quAmountOut,
+ locals.poolBasicState.reservedAssetAmount,
+ locals.poolBasicState.reservedQuAmount,
+ state.swapFeeRate,
+ locals.i1,
+ locals.i2,
+ locals.i3
+ );
+
+ // above call overflow
+ if (locals.assetAmountIn == -1) {
+ return;
+ }
+
+ // no overflow risk
+ // input.quAmountOut * state.swapFeeRate / (QSWAP_SWAP_FEE_BASE - state.swapFeeRate) * state.protocolFeeRate / QSWAP_PROTOCOL_FEE_BASE
+ locals.protocolFee = sint64(div(
+ div(
+ uint128(input.quAmountOut) * uint128(state.swapFeeRate),
+ uint128(QSWAP_SWAP_FEE_BASE - state.swapFeeRate)
+ ) * uint128(state.protocolFeeRate),
+ uint128(QSWAP_PROTOCOL_FEE_BASE)
+ ).low
+ );
+
+ // user does not hold enough asset
+ if (qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ qpi.invocator(),
+ qpi.invocator(),
+ SELF_INDEX,
+ SELF_INDEX
+ ) < locals.assetAmountIn ) {
+ return;
+ }
+
+ // not meet user amountIn reqiurement
+ if (locals.assetAmountIn > input.assetAmountInMax) {
+ return;
+ }
+
+ locals.transferredAssetAmountBefore = qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ SELF_INDEX,
+ SELF_INDEX
+ );
+ qpi.transferShareOwnershipAndPossession(
+ input.assetName,
+ input.assetIssuer,
+ qpi.invocator(),
+ qpi.invocator(),
+ locals.assetAmountIn,
+ SELF
+ );
+ locals.transferredAssetAmountAfter = qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ SELF,
+ SELF,
+ SELF_INDEX,
+ SELF_INDEX
+ );
+
+ if (locals.transferredAssetAmountAfter - locals.transferredAssetAmountBefore < locals.assetAmountIn) {
+ return;
+ }
+
+ qpi.transfer(qpi.invocator(), input.quAmountOut);
+ output.assetAmountIn = locals.assetAmountIn;
+
+ // update pool states
+ locals.poolBasicState.reservedAssetAmount += locals.assetAmountIn;
+ locals.poolBasicState.reservedQuAmount -= input.quAmountOut;
+ locals.poolBasicState.reservedQuAmount -= locals.protocolFee;
+
+ state.protocolEarnedFee += locals.protocolFee;
+
+ state.mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
+ _
+
+ struct TransferShareOwnershipAndPossession_locals {
+ QX::Fees_input feesInput;
+ QX::Fees_output feesOutput;
+ };
+
+ PUBLIC_PROCEDURE_WITH_LOCALS(TransferShareOwnershipAndPossession)
+ output.transferredAmount = 0;
+
+ CALL_OTHER_CONTRACT_FUNCTION(QX, Fees, locals.feesInput, locals.feesOutput);
+
+ if (qpi.invocationReward() < locals.feesOutput.transferFee){
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ return;
+ }
+
+ if (input.amount <= 0){
+ if (qpi.invocationReward() > 0 ) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ }
+ return;
+ }
+
+ if (qpi.numberOfPossessedShares(
+ input.assetName,
+ input.assetIssuer,
+ qpi.invocator(),
+ qpi.invocator(),
+ SELF_INDEX,
+ SELF_INDEX
+ ) < input.amount) {
+
+ if (qpi.invocationReward() > 0 ) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ }
+ return;
+ }
+
+ output.transferredAmount = qpi.transferShareOwnershipAndPossession(
+ input.assetName,
+ input.assetIssuer,
+ qpi.invocator(),
+ qpi.invocator(),
+ input.amount,
+ input.newOwnerAndPossessor
+ ) < 0 ? 0 : input.amount;
+
+ if (output.transferredAmount == 0) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward());
+ } else if (qpi.invocationReward() > locals.feesOutput.transferFee) {
+ qpi.transfer(qpi.invocator(), qpi.invocationReward() - locals.feesOutput.transferFee);
+ }
+
+ state.protocolEarnedFee += locals.feesOutput.transferFee;
+ _
+
+ REGISTER_USER_FUNCTIONS_AND_PROCEDURES
+ // functions
+ REGISTER_USER_FUNCTION(Fees, 1);
+ REGISTER_USER_FUNCTION(GetPoolBasicState, 2);
+ REGISTER_USER_FUNCTION(GetLiqudityOf, 3);
+ REGISTER_USER_FUNCTION(QuoteExactQuInput, 4);
+ REGISTER_USER_FUNCTION(QuoteExactQuOutput, 5);
+ REGISTER_USER_FUNCTION(QuoteExactAssetInput, 6);
+ REGISTER_USER_FUNCTION(QuoteExactAssetOutput, 7);
+
+ // procedure
+ REGISTER_USER_PROCEDURE(IssueAsset, 1);
+ REGISTER_USER_PROCEDURE(TransferShareOwnershipAndPossession, 2);
+ REGISTER_USER_PROCEDURE(CreatePool, 3);
+ REGISTER_USER_PROCEDURE(AddLiqudity, 4);
+ REGISTER_USER_PROCEDURE(RemoveLiqudity, 5);
+ REGISTER_USER_PROCEDURE(SwapExactQuForAsset, 6);
+ REGISTER_USER_PROCEDURE(SwapQuForExactAsset, 7);
+ REGISTER_USER_PROCEDURE(SwapExactAssetForQu, 8);
+ REGISTER_USER_PROCEDURE(SwapAssetForExactQu, 9);
+ _
+
+ INITIALIZE
+ state.swapFeeRate = 30; // 0.3%, must less than 10000
+ state.protocolFeeRate = 20; // 20%, must less than 100
+ state.poolCreationRate = 20; // 20%, must less than 100
+ _
+
+ END_TICK
+ if ((div((state.protocolEarnedFee - state.distributedAmount), 676ULL) > 0) && (state.protocolEarnedFee > state.distributedAmount))
+ {
+ if (qpi.distributeDividends(div((state.protocolEarnedFee - state.distributedAmount), 676ULL)))
+ {
+ state.distributedAmount += div((state.protocolEarnedFee- state.distributedAmount), 676ULL) * NUMBER_OF_COMPUTORS;
+ }
+ }
+ _
+};
diff --git a/src/contracts/qpi.h b/src/contracts/qpi.h
index a1277b8a..f2bd2fa4 100644
--- a/src/contracts/qpi.h
+++ b/src/contracts/qpi.h
@@ -5,6 +5,9 @@
// m256i is used for the id data type
#include "../platform/m256.h"
+// uint128
+#include "../platform/uint128.h"
+
// ASSERT can be used to support debugging and speed-up development
#include "../platform/assert.h"
@@ -46,6 +49,7 @@ namespace QPI
typedef signed long long sint64;
typedef unsigned long long uint64;
+ typedef uint128_t uint128;
typedef m256i id;
#define NULL_ID id::zero()
@@ -588,7 +592,7 @@ namespace QPI
template
inline static T div(T a, T b)
{
- return b ? (a / b) : 0;
+ return b ? (a / b) : T(0);
}
// Return remainder of dividing a by b, but return 0 if b is 0 (requires modulo % operator)
@@ -1403,6 +1407,11 @@ namespace QPI
const AssetPossessionSelect& possession = AssetPossessionSelect::any()
) const;
+ inline bool isAssetIssued(
+ const m256i& id,
+ unsigned long long assetName
+ ) const;
+
// Returns -1 if the current tick is empty, returns the number of the transactions in the tick otherwise, including 0.
inline sint32 numberOfTickTransactions(
) const;
diff --git a/src/platform/uint128.h b/src/platform/uint128.h
new file mode 100644
index 00000000..a55950d2
--- /dev/null
+++ b/src/platform/uint128.h
@@ -0,0 +1,284 @@
+// Copyright (c) 2013 - 2018 Jason Lee @ calccrypto at gmail.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+// https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp
+
+#pragma once
+
+class uint128_t{
+public:
+ unsigned __int64 low;
+ unsigned __int64 high;
+
+ uint128_t(unsigned __int64 n){
+ low = n;
+ high = 0;
+ };
+
+ uint128_t(unsigned __int64 i_high, unsigned __int64 i_low){
+ high = i_high;
+ low = i_low;
+ }
+
+ // transfrom
+ operator bool() const {
+ return (bool) (high | low);
+ }
+
+ // compare
+ bool operator==(const uint128_t & rhs) const{
+ return ((high == rhs.high) && (low == rhs.low));
+ }
+
+ bool operator<(const uint128_t & rhs) const{
+ if (high == rhs.high) {
+ return low < rhs.low;
+ } else {
+ return high < rhs.high;
+ }
+ }
+
+ bool operator>(const uint128_t & rhs) const{
+ if (high == rhs.high) {
+ return low > rhs.low;
+ } else {
+ return high > rhs.high;
+ }
+ }
+
+ bool operator>=(const uint128_t & rhs) const{
+ return ((*this > rhs) || (*this == rhs));
+ }
+
+ bool operator<=(const uint128_t & rhs) const{
+ return ((*this < rhs) || (*this == rhs));
+ }
+
+ uint128_t operator<<(const uint128_t & rhs) const{
+ const unsigned __int64 shift = rhs.low;
+ if (((bool) rhs.high) || (shift >= 128)){
+ return uint128_t(0, 0);
+ }
+ else if (shift == 64){
+ return uint128_t(low, 0);
+ }
+ else if (shift == 0){
+ return *this;
+ }
+ else if (shift < 64){
+ return uint128_t((high << shift) + (low >> (64 - shift)), low << shift);
+ }
+ else if ((128 > shift) && (shift > 64)){
+ return uint128_t(low << (shift - 64), 0);
+ }
+ else{
+ return uint128_t(0, 0);
+ }
+ }
+
+ uint128_t & operator<<=(const uint128_t & rhs){
+ *this = *this << rhs;
+ return *this;
+ }
+
+ uint128_t operator>>(const uint128_t & rhs) const{
+ const unsigned __int64 shift = rhs.low;
+ if (((bool) rhs.high) || (shift >= 128)){
+ return uint128_t(0, 0);
+ }
+ else if (shift == 64){
+ return uint128_t(0, high);
+ }
+ else if (shift == 0){
+ return *this;
+ }
+ else if (shift < 64){
+ return uint128_t(high >> shift, (high << (64 - shift)) + (low >> shift));
+ }
+ else if ((128 > shift) && (shift > 64)){
+ return uint128_t(0, (high >> (shift - 64)));
+ }
+ else{
+ return uint128_t(0, 0);
+ }
+ }
+
+ uint128_t & operator>>=(const uint128_t & rhs){
+ *this = *this >> rhs;
+ return *this;
+ }
+
+ uint128_t operator&(const uint128_t & rhs) const{
+ return uint128_t(high & rhs.high, low & rhs.low);
+ }
+
+ uint128_t operator&(const int & rhs) const{
+ return (*this) & uint128_t(0, rhs);
+ }
+
+ uint128_t & operator&=(const uint128_t & rhs){
+ high &= rhs.high;
+ low &= rhs.low;
+ return *this;
+ }
+
+ uint128_t operator>>(const unsigned int & rhs) const{
+ return *this >> uint128_t(0, rhs);
+ }
+
+ // bits
+ unsigned __int8 bits() const{
+ unsigned __int8 out = 0;
+ if (high){
+ out = 64;
+ unsigned __int64 up = high;
+ while (up){
+ up >>= 1;
+ out++;
+ }
+ }
+ else{
+ unsigned __int64 inner_low = low;
+ while (inner_low){
+ inner_low >>= 1;
+ out++;
+ }
+ }
+ return out;
+ }
+
+ // calculation
+ uint128_t operator+(const uint128_t & rhs) const{
+ return uint128_t(high + rhs.high+ ((low+ rhs.low) < low), low + rhs.low);
+ }
+
+ uint128_t operator-(const uint128_t & rhs) const{
+ return uint128_t(high - rhs.high - ((low - rhs.low) > low), low - rhs.low);
+ }
+
+ uint128_t & operator-=(const uint128_t & rhs){
+ *this = *this - rhs;
+ return *this;
+ }
+
+ uint128_t & operator+=(const uint128_t & rhs){
+ high += rhs.high + ((low + rhs.low) < low);
+ low += rhs.low;
+ return *this;
+ }
+
+ uint128_t & operator++(){
+ return *this += uint128_t(0, 1);
+ }
+
+ void divmod(const uint128_t & lhs, const uint128_t & rhs, uint128_t& q, uint128_t& r) const{
+ // Save some calculations /////////////////////
+ //if (rhs == uint128_0){
+ // throw std::domain_error("Error: division or modulus by 0");
+ //}
+ if (rhs == uint128_t(0, 1)){
+ q = lhs;
+ r = uint128_t(0, 0);
+ return;
+ }
+ else if (lhs == rhs){
+ q = uint128_t(0, 1);
+ r = uint128_t(0, 0);
+ return;
+ }
+ else if ((lhs == uint128_t(0, 0)) || (lhs < rhs)){
+ q = uint128_t(0, 0);
+ r = lhs;
+ return;
+ }
+
+ q = uint128_t(0, 0);
+ r = uint128_t(0, 0);
+ for(unsigned char x = lhs.bits(); x > 0; x--){
+ q <<= uint128_t(0, 1);
+ r <<= uint128_t(0, 1);
+
+ if ((lhs >> (x - 1U)) & 1){
+ // ++qr.second;
+ ++r;
+ }
+
+ if (r >= rhs) {
+ r -= rhs;
+ ++q;
+ }
+ }
+ }
+
+ uint128_t operator/(const uint128_t & rhs) const{
+ uint128_t q{ 0, 0 };
+ uint128_t r{ 0, 0 };
+ divmod(*this, rhs, q, r);
+ return q;
+ }
+
+ uint128_t operator*(const uint128_t& rhs) const{
+ // split values into 4 32-bit parts
+ unsigned __int64 top[4] = {high >> 32, high & 0xffffffff, low >> 32, low & 0xffffffff};
+
+ unsigned __int64 bottom[4] = {rhs.high >> 32, rhs.high & 0xffffffff, rhs.low >> 32, rhs.low & 0xffffffff};
+ unsigned __int64 products[4][4];
+
+ // multiply each component of the values
+ for(int y = 3; y > -1; y--){
+ for(int x = 3; x > -1; x--){
+ products[3 - x][y] = top[x] * bottom[y];
+ }
+ }
+
+ // first row
+ unsigned __int64 fourth32 = (products[0][3] & 0xffffffff);
+ unsigned __int64 third32 = (products[0][2] & 0xffffffff) + (products[0][3] >> 32);
+ unsigned __int64 second32 = (products[0][1] & 0xffffffff) + (products[0][2] >> 32);
+ unsigned __int64 first32 = (products[0][0] & 0xffffffff) + (products[0][1] >> 32);
+
+ // second row
+ third32 += (products[1][3] & 0xffffffff);
+ second32 += (products[1][2] & 0xffffffff) + (products[1][3] >> 32);
+ first32 += (products[1][1] & 0xffffffff) + (products[1][2] >> 32);
+
+ // third row
+ second32 += (products[2][3] & 0xffffffff);
+ first32 += (products[2][2] & 0xffffffff) + (products[2][3] >> 32);
+
+ // fourth row
+ first32 += (products[3][3] & 0xffffffff);
+
+ // move carry to next digit
+ third32 += fourth32 >> 32;
+ second32 += third32 >> 32;
+ first32 += second32 >> 32;
+
+ // remove carry from current digit
+ fourth32 &= 0xffffffff;
+ third32 &= 0xffffffff;
+ second32 &= 0xffffffff;
+ first32 &= 0xffffffff;
+
+ // printf("%llx, %llx, %llx, %llx\n", fourth32, second32, third32, fourth32);
+
+ // combine components
+ return uint128_t((first32<<32|second32), (third32<<32|fourth32));
+ }
+};
\ No newline at end of file
diff --git a/test/contract_swap.cpp b/test/contract_swap.cpp
new file mode 100644
index 00000000..e9909874
--- /dev/null
+++ b/test/contract_swap.cpp
@@ -0,0 +1,715 @@
+#define NO_UEFI
+
+#include "contract_testing.h"
+
+//#define PRINT_DETAILS 0
+
+static constexpr uint64 QSWAP_ISSUE_ASSET_FEE = 1000000000ull;
+static constexpr uint64 QSWAP_TRANSFER_ASSET_FEE = 10000000ull;
+static constexpr uint64 QSWAP_CREATE_POOL_FEE = 1000000000ull;
+
+static const id QSWAP_CONTRACT_ID(QSWAP_CONTRACT_INDEX, 0, 0, 0);
+
+//constexpr uint32 SWAP_FEE_IDX = 1;
+constexpr uint32 GET_POOL_BASIC_STATE_IDX = 2;
+constexpr uint32 GET_LIQUDITY_OF_IDX = 3;
+constexpr uint32 QUOTE_EXACT_QU_INPUT_IDX = 4;
+constexpr uint32 QUOTE_EXACT_QU_OUTPUT_IDX = 5;
+constexpr uint32 QUOTE_EXACT_ASSET_INPUT_IDX = 6;
+constexpr uint32 QUOTE_EXACT_ASSET_OUTPUT_IDX = 7;
+//
+constexpr uint32 ISSUE_ASSET_IDX = 1;
+constexpr uint32 TRANSFER_SHARE_OWNERSHIP_AND_POSSESSION_IDX = 2;
+constexpr uint32 CREATE_POOL_IDX = 3;
+constexpr uint32 ADD_LIQUDITY_IDX = 4;
+constexpr uint32 REMOVE_LIQUDITY_IDX = 5;
+constexpr uint32 SWAP_EXACT_QU_FOR_ASSET_IDX = 6;
+constexpr uint32 SWAP_QU_FOR_EXACT_ASSET_IDX = 7;
+constexpr uint32 SWAP_EXACT_ASSET_FOR_QU_IDX = 8;
+constexpr uint32 SWAP_ASSET_FOR_EXACT_QU_IDX = 9;
+
+//std::string assetNameFromInt64(uint64 assetName);
+
+class QswapChecker : public QSWAP
+{
+// public:
+// void checkCollectionConsistency() {
+// }
+};
+
+
+class ContractTestingQswap : protected ContractTesting
+{
+public:
+ ContractTestingQswap()
+ {
+ initEmptySpectrum();
+ initEmptyUniverse();
+ INIT_CONTRACT(QSWAP);
+ callSystemProcedure(QSWAP_CONTRACT_INDEX, INITIALIZE);
+ INIT_CONTRACT(QX);
+ callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE);
+ }
+
+ QswapChecker* getState()
+ {
+ return (QswapChecker*)contractStates[QSWAP_CONTRACT_INDEX];
+ }
+
+ bool loadState(const CHAR16* filename)
+ {
+ return load(filename, sizeof(QSWAP), contractStates[QSWAP_CONTRACT_INDEX]) == sizeof(QSWAP);
+ }
+
+ sint64 issueAsset(const id& issuer, QSWAP::IssueAsset_input input){
+ QSWAP::IssueAsset_output output;
+ invokeUserProcedure(QSWAP_CONTRACT_INDEX, ISSUE_ASSET_IDX, input, output, issuer, QSWAP_ISSUE_ASSET_FEE);
+ return output.issuedNumberOfShares;
+ }
+
+ sint64 transferAsset(const id& issuer, QSWAP::TransferShareOwnershipAndPossession_input input){
+ QSWAP::TransferShareOwnershipAndPossession_output output;
+ invokeUserProcedure(QSWAP_CONTRACT_INDEX, TRANSFER_SHARE_OWNERSHIP_AND_POSSESSION_IDX, input, output, issuer, QSWAP_TRANSFER_ASSET_FEE);
+ return output.transferredAmount;
+ }
+
+ bool createPool(const id& issuer, uint64 assetName){
+ QSWAP::CreatePool_input input{issuer, assetName};
+ QSWAP::CreatePool_output output;
+ invokeUserProcedure(QSWAP_CONTRACT_INDEX, CREATE_POOL_IDX, input, output, issuer, QSWAP_CREATE_POOL_FEE);
+ return output.success;
+ }
+
+ QSWAP::GetPoolBasicState_output getPoolBasicState(const id& issuer, uint64 assetName){
+ QSWAP::GetPoolBasicState_input input{issuer, assetName};
+ QSWAP::GetPoolBasicState_output output;
+
+ callFunction(QSWAP_CONTRACT_INDEX, GET_POOL_BASIC_STATE_IDX, input, output);
+ return output;
+ }
+
+ QSWAP::AddLiqudity_output addLiqudity(const id& issuer, QSWAP::AddLiqudity_input input, uint64 inputValue){
+ QSWAP::AddLiqudity_output output;
+ invokeUserProcedure(
+ QSWAP_CONTRACT_INDEX,
+ ADD_LIQUDITY_IDX,
+ input,
+ output,
+ issuer,
+ inputValue
+ );
+ return output;
+ }
+
+ QSWAP::RemoveLiqudity_output removeLiqudity(const id& issuer, QSWAP::RemoveLiqudity_input input, uint64 inputValue){
+ QSWAP::RemoveLiqudity_output output;
+ invokeUserProcedure(
+ QSWAP_CONTRACT_INDEX,
+ REMOVE_LIQUDITY_IDX,
+ input,
+ output,
+ issuer,
+ inputValue
+ );
+ return output;
+ }
+
+ QSWAP::GetLiqudityOf_output getLiqudityOf(QSWAP::GetLiqudityOf_input input){
+ QSWAP::GetLiqudityOf_output output;
+ callFunction(QSWAP_CONTRACT_INDEX, GET_LIQUDITY_OF_IDX, input, output);
+ return output;
+ }
+
+ QSWAP::SwapExactQuForAsset_output swapExactQuForAsset( const id& issuer, QSWAP::SwapExactQuForAsset_input input, uint64 inputValue) {
+ QSWAP::SwapExactQuForAsset_output output;
+ invokeUserProcedure(
+ QSWAP_CONTRACT_INDEX,
+ SWAP_EXACT_QU_FOR_ASSET_IDX,
+ input,
+ output,
+ issuer,
+ inputValue
+ );
+
+ return output;
+ }
+
+ QSWAP::SwapQuForExactAsset_output swapQuForExactAsset( const id& issuer, QSWAP::SwapQuForExactAsset_input input, uint64 inputValue) {
+ QSWAP::SwapQuForExactAsset_output output;
+ invokeUserProcedure(
+ QSWAP_CONTRACT_INDEX,
+ SWAP_QU_FOR_EXACT_ASSET_IDX,
+ input,
+ output,
+ issuer,
+ inputValue
+ );
+
+ return output;
+ }
+
+ QSWAP::SwapExactAssetForQu_output swapExactAssetForQu(const id& issuer, QSWAP::SwapExactAssetForQu_input input, uint64 inputValue) {
+ QSWAP::SwapExactAssetForQu_output output;
+ invokeUserProcedure(
+ QSWAP_CONTRACT_INDEX,
+ SWAP_EXACT_ASSET_FOR_QU_IDX,
+ input,
+ output,
+ issuer,
+ inputValue
+ );
+
+ return output;
+ }
+
+ QSWAP::SwapAssetForExactQu_output swapAssetForExactQu(const id& issuer, QSWAP::SwapAssetForExactQu_input input, uint64 inputValue) {
+ QSWAP::SwapAssetForExactQu_output output;
+ invokeUserProcedure(
+ QSWAP_CONTRACT_INDEX,
+ SWAP_ASSET_FOR_EXACT_QU_IDX,
+ input,
+ output,
+ issuer,
+ inputValue
+ );
+
+ return output;
+ }
+
+ QSWAP::QuoteExactQuInput_output quoteExactQuInput(QSWAP::QuoteExactQuInput_input input) {
+ QSWAP::QuoteExactQuInput_output output;
+ callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_QU_INPUT_IDX, input, output);
+ return output;
+ }
+
+ QSWAP::QuoteExactQuOutput_output quoteExactQuOutput(QSWAP::QuoteExactQuOutput_input input) {
+ QSWAP::QuoteExactQuOutput_output output;
+ callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_QU_OUTPUT_IDX, input, output);
+ return output;
+ }
+
+ QSWAP::QuoteExactAssetInput_output quoteExactAssetInput(QSWAP::QuoteExactAssetInput_input input){
+ QSWAP::QuoteExactAssetInput_output output;
+ callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_ASSET_INPUT_IDX, input, output);
+ return output;
+ }
+
+ QSWAP::QuoteExactAssetOutput_output quoteExactAssetOutput(QSWAP::QuoteExactAssetOutput_input input){
+ QSWAP::QuoteExactAssetOutput_output output;
+ callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_ASSET_OUTPUT_IDX, input, output);
+ return output;
+ }
+};
+
+TEST(ContractSwap, QuoteTest)
+{
+ ContractTestingQswap qswap;
+
+ id issuer(1, 2, 3, 4);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ sint64 numberOfShares = 10000 * 1000;
+
+ // issue an asset and create a pool, and init liqudity
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares);
+
+ increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE);
+ EXPECT_TRUE(qswap.createPool(issuer, assetName));
+
+ sint64 inputValue = 30*1000;
+ increaseEnergy(issuer, inputValue);
+ QSWAP::AddLiqudity_input alInput = { issuer, assetName, 30*1000, 0, 0 };
+ QSWAP::AddLiqudity_output output = qswap.addLiqudity(issuer, alInput, inputValue);
+
+ QSWAP::QuoteExactQuInput_input qi_input = {issuer, assetName, 1000};
+ QSWAP::QuoteExactQuInput_output qi_output = qswap.quoteExactQuInput(qi_input);
+ // printf("quote exact qu input: %lld\n", qi_output.assetAmountOut);
+ EXPECT_EQ(qi_output.assetAmountOut, 964);
+
+ QSWAP::QuoteExactQuOutput_input qo_input = {issuer, assetName, 1000};
+ QSWAP::QuoteExactQuOutput_output qo_output = qswap.quoteExactQuOutput(qo_input);
+ // printf("quote exact qu output: %lld\n", qo_output.assetAmountIn);
+ EXPECT_EQ(qo_output.assetAmountIn, 1037);
+
+ QSWAP::QuoteExactAssetInput_input ai_input = {issuer, assetName, 1000};
+ QSWAP::QuoteExactAssetInput_output ai_output = qswap.quoteExactAssetInput(ai_input);
+ // printf("quote exact asset input: %lld\n", ai_output.quAmountOut);
+ EXPECT_EQ(ai_output.quAmountOut, 964);
+
+ QSWAP::QuoteExactAssetOutput_input ao_input = {issuer, assetName, 1000};
+ QSWAP::QuoteExactAssetOutput_output ao_output = qswap.quoteExactAssetOutput(ao_input);
+ // printf("quote exact asset output: %lld\n", ao_output.quAmountIn);
+ EXPECT_EQ(ao_output.quAmountIn, 1037);
+ }
+}
+
+/*
+0. normally issue asset
+1. not enough qu for asset issue fee
+2. issue duplicate asset
+3. issue asset with invalid input params, such as numberOfShares: 0
+*/
+TEST(ContractSwap, IssueAsset)
+{
+ ContractTestingQswap qswap;
+
+ id issuer(1, 2, 3, 4);
+
+ // 0. normally issue asset and transfer
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ sint64 numberOfShares = 1000000;
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(getBalance(QSWAP_CONTRACT_ID), 0);
+ EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares);
+ EXPECT_EQ(getBalance(QSWAP_CONTRACT_ID), QSWAP_ISSUE_ASSET_FEE);
+
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ sint64 transferAmount = 1000;
+ id newId(2,3,4,5);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, newId, newId, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), 0);
+ QSWAP::TransferShareOwnershipAndPossession_input ts_input = {issuer, assetName, newId, transferAmount};
+ // printf("ts amount: %lld\n", transferAmount);
+ EXPECT_EQ(qswap.transferAsset(issuer, ts_input), transferAmount);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, newId, newId, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), transferAmount);
+ // printf("%lld\n", getBalance(QSWAP_CONTRACT_ID));
+ }
+
+ // 1. not enough energy for asset issue fee
+ {
+ decreaseEnergy(spectrumIndex(issuer), getBalance(issuer));
+ uint64 assetName = assetNameFromString("QSWAP1");
+ sint64 numberOfShares = 1000000;
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), 0);
+ }
+
+ // 2. issue duplicate asset, related to test.0
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ sint64 numberOfShares = 1000000;
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), 0);
+ }
+
+ // 3. issue asset with invalid input params, such as numberOfShares: 0
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ uint64 assetName = assetNameFromString("QSWAP1");
+ sint64 numberOfShares = 0;
+ QSWAP::IssueAsset_input input = {assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), 0);
+ }
+}
+
+TEST(ContractSwap, SwapExactQuForAsset)
+{
+ ContractTestingQswap qswap;
+
+ id issuer(1, 2, 3, 4);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ sint64 numberOfShares = 10000 * 1000;
+
+ // issue an asset and create a pool, and init liqudity
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares);
+
+ increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE);
+ EXPECT_TRUE(qswap.createPool(issuer, assetName));
+
+ sint64 inputValue = 200*1000;
+ increaseEnergy(issuer, inputValue);
+ QSWAP::AddLiqudity_input alInput = { issuer, assetName, 100*1000, 0, 0 };
+ QSWAP::AddLiqudity_output output = qswap.addLiqudity(issuer, alInput, inputValue);
+ // printf("increase liqudity: %lld, %lld, %lld\n", output.userIncreaseLiqudity, output.assetAmount, output.quAmount);
+ }
+
+ {
+ // swap in 100*1000 qu, get about 1000*50 asset
+ id user(2,3,4,5);
+ sint64 inputValue = 200*1000;
+ increaseEnergy(user, inputValue);
+
+ QSWAP::QuoteExactQuInput_input qi_input = {issuer, assetName, inputValue};
+ QSWAP::QuoteExactQuInput_output qi_output = qswap.quoteExactQuInput(qi_input);
+ // printf("quote_exact_qu_input, asset out: %lld\n", qi_output.assetAmountOut);
+
+ QSWAP::SwapExactQuForAsset_input input = {issuer, assetName, 0};
+ QSWAP::SwapExactQuForAsset_output output = qswap.swapExactQuForAsset(user, input, inputValue);
+ // printf("swap_exact_qu_for_asset, asset out: %lld\n", output.assetAmountOut);
+
+ EXPECT_EQ(qi_output.assetAmountOut, output.assetAmountOut); // 49924
+
+ EXPECT_TRUE(output.assetAmountOut <= 50000); // 49924 if swapFee 0.3%
+
+ QSWAP::GetPoolBasicState_output psOutput = qswap.getPoolBasicState(issuer, assetName);
+ // printf("%lld, %lld, %lld\n", psOutput.reservedAssetAmount, psOutput.reservedQuAmount, psOutput.totalLiqudity);
+ EXPECT_TRUE(psOutput.reservedQuAmount >= (200000*2 - 200)); // 399880 if swapFee 0.3%, protocolFee 20%
+ EXPECT_TRUE(psOutput.reservedAssetAmount >= 50000 ); // 50076
+ EXPECT_EQ(psOutput.totalLiqudity, 141421); // liqudity stay the same
+ }
+}
+
+TEST(ContractSwap, SwapQuForExactAsset)
+{
+ ContractTestingQswap qswap;
+
+ id issuer(1, 2, 3, 4);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ sint64 numberOfShares = 10000 * 1000;
+
+ // issue an asset and create a pool, and init liqudity
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares);
+
+ increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE);
+ EXPECT_TRUE(qswap.createPool(issuer, assetName));
+
+ sint64 inputValue = 200*1000;
+ increaseEnergy(issuer, inputValue);
+ QSWAP::AddLiqudity_input alInput = { issuer, assetName, 100*1000, 0, 0 };
+ QSWAP::AddLiqudity_output output = qswap.addLiqudity(issuer, alInput, inputValue);
+ // printf("increase liqudity: %lld, %lld, %lld\n", output.userIncreaseLiqudity, output.assetAmount, output.quAmount);
+ }
+
+ {
+ id user(2,3,4,5);
+ sint64 inputValue = 1000 * 200;
+ sint64 expectQuAmountIn = 22289;
+ sint64 assetAmountOut = 10 * 1000;
+ increaseEnergy(user, inputValue);
+
+ QSWAP::QuoteExactAssetOutput_input ao_input = {issuer, assetName, assetAmountOut};
+ QSWAP::QuoteExactAssetOutput_output ao_output = qswap.quoteExactAssetOutput(ao_input);
+ // printf("quote_exact_asset_output, qu in %lld\n", ao_output.quAmountIn);
+
+ QSWAP::SwapQuForExactAsset_input input = {issuer, assetName, assetAmountOut};
+ QSWAP::SwapQuForExactAsset_output output = qswap.swapQuForExactAsset(user, input, inputValue);
+
+ EXPECT_EQ(ao_output.quAmountIn, output.quAmountIn); // 22289
+
+ // EXPECT_EQ(output.quAmountIn, 22289);
+ // printf("swap_qu_for_exact_asset, asset in: %lld\n", output.quAmountIn);
+ }
+}
+
+TEST(ContractSwap, SwapExactAssetForQu)
+{
+ ContractTestingQswap qswap;
+
+ id issuer(1, 2, 3, 4);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ sint64 numberOfShares = 10000 * 1000;
+
+ // issue an asset and create a pool, and init liqudity
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares);
+
+ increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE);
+ EXPECT_TRUE(qswap.createPool(issuer, assetName));
+
+ sint64 inputValue = 200*1000;
+ increaseEnergy(issuer, inputValue);
+ QSWAP::AddLiqudity_input alInput = { issuer, assetName, 100*1000, 0, 0 };
+ QSWAP::AddLiqudity_output output = qswap.addLiqudity(issuer, alInput, inputValue);
+ // printf("increase liqudity: %lld, %lld, %lld\n", output.userIncreaseLiqudity, output.assetAmount, output.quAmount);
+ }
+
+ {
+ id user(1, 2,3,4);
+ sint64 inputValue = 0;
+ sint64 assetAmountIn = 100*1000;
+ sint64 expectQuAmountOut = 99700;
+ increaseEnergy(user, inputValue);
+
+ QSWAP::QuoteExactAssetInput_input ai_input = {issuer, assetName, assetAmountIn};
+ QSWAP::QuoteExactAssetInput_output ai_output = qswap.quoteExactAssetInput(ai_input);
+ // printf("quote exact asset input: %lld\n", ai_output.quAmountOut);
+
+ QSWAP::SwapExactAssetForQu_input input = {issuer, assetName, assetAmountIn, 0};
+ QSWAP::SwapExactAssetForQu_output output = qswap.swapExactAssetForQu(user, input, inputValue);
+ // printf("swap qu out: %lld\n", output.quAmountOut);
+ EXPECT_EQ(ai_output.quAmountOut, output.quAmountOut); // 99700
+ }
+}
+
+TEST(ContractSwap, SwapAssetForExactQu)
+{
+ ContractTestingQswap qswap;
+
+ id issuer(1, 2, 3, 4);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ sint64 numberOfShares = 10000 * 1000;
+
+ // issue an asset and create a pool, and init liqudity
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares);
+
+ increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE);
+ EXPECT_TRUE(qswap.createPool(issuer, assetName));
+
+ sint64 inputValue = 200*1000;
+ increaseEnergy(issuer, inputValue);
+ QSWAP::AddLiqudity_input alInput = { issuer, assetName, 100*1000, 0, 0 };
+ QSWAP::AddLiqudity_output output = qswap.addLiqudity(issuer, alInput, inputValue);
+ // printf("increase liqudity: %lld, %lld, %lld\n", output.userIncreaseLiqudity, output.assetAmount, output.quAmount);
+
+ // QSWAP::GetPoolBasicState_output gp_output = qswap.getPoolBasicState(issuer, assetName);
+ // printf("%lld, %lld, %lld\n", gp_output.reservedQuAmount, gp_output.reservedAssetAmount, gp_output.totalLiqudity);
+ }
+
+ {
+ id user(1,2,3,4);
+ sint64 inputValue = 0;
+ sint64 quAmountOut = 200*1000 - 1;
+
+ QSWAP::QuoteExactQuOutput_input qo_input = {issuer, assetName, quAmountOut};
+ QSWAP::QuoteExactQuOutput_output qo_output = qswap.quoteExactQuOutput(qo_input);
+ // printf("quote exact qu output: %lld\n", qo_output.assetAmountIn);
+ EXPECT_EQ(qo_output.assetAmountIn, -1);
+ }
+
+ {
+ id user(1,2,3,4);
+ sint64 inputValue = 0;
+ sint64 quAmountOut = 100*1000;
+ sint64 expectAssetAmountIn = 100603;
+
+ QSWAP::QuoteExactQuOutput_input qo_input = {issuer, assetName, quAmountOut};
+ QSWAP::QuoteExactQuOutput_output qo_output = qswap.quoteExactQuOutput(qo_input);
+ // printf("quote exact qu output: %lld\n", qo_output.assetAmountIn);
+ EXPECT_EQ(qo_output.assetAmountIn, expectAssetAmountIn);
+
+ increaseEnergy(user, inputValue);
+ sint64 assetAmountInMax = 200*1000;
+ QSWAP::SwapAssetForExactQu_input input = {issuer, assetName, assetAmountInMax, quAmountOut};
+ QSWAP::SwapAssetForExactQu_output output = qswap.swapAssetForExactQu(user, input, inputValue);
+ // printf("swap asset in: %lld\n", output.assetAmountIn);
+ EXPECT_EQ(qo_output.assetAmountIn, output.assetAmountIn);
+ }
+}
+
+/*
+0. check pool state before create
+1. normal create pool, check pool existance, pool states
+2. create duplicate pool
+3. create pool with invalid asset
+*/
+TEST(ContractSwap, CreatePool)
+{
+ ContractTestingQswap qswap;
+
+ id issuer(1, 2, 3, 4);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ sint64 numberOfShares = 1000000;
+
+ // issue asset first
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ QSWAP::IssueAsset_input input = {assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares);
+ }
+
+ // 0. check not exsit pool state before create
+ {
+ QSWAP::GetPoolBasicState_output output = qswap.getPoolBasicState(issuer, assetName);
+ EXPECT_FALSE(output.poolExists);
+ }
+
+ // 1. normal create pool, check pool existance, pool states
+ {
+ increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE);
+ EXPECT_TRUE(qswap.createPool(issuer, assetName));
+
+ // initial pool state
+ QSWAP::GetPoolBasicState_output output = qswap.getPoolBasicState(issuer, assetName);
+ EXPECT_EQ(output.poolExists, true);
+ EXPECT_EQ(output.reservedQuAmount, 0);
+ EXPECT_EQ(output.reservedAssetAmount, 0);
+ EXPECT_EQ(output.totalLiqudity, 0);
+ }
+
+ // 2. create duplicate pool
+ {
+ EXPECT_FALSE(qswap.createPool(issuer, assetName));
+ }
+
+ // 3. ceate pool with not issued asset
+ {
+ uint64 assetName2 = assetNameFromString("QswapX");
+ EXPECT_FALSE(qswap.createPool(issuer, assetName2));
+ }
+}
+
+/*
+add liqudity 2 times, and then remove
+*/
+TEST(ContractSwap, LiqTest1)
+{
+ ContractTestingQswap qswap;
+
+ id issuer(1, 2, 3, 4);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ uint64 invalidAssetName = assetNameFromString("QSWAP1");
+ sint64 numberOfShares = 1000*1000;
+
+ // 0. issue an asset and create a pool
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares);
+
+ increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE);
+ EXPECT_TRUE(qswap.createPool(issuer, assetName));
+ }
+
+ // 1. add liqudity to a initial pool, first time
+ {
+ sint64 quStakeAmount = 200*1000;
+ sint64 inputValue = quStakeAmount;
+ sint64 assetStakeAmount = 100*1000;
+ increaseEnergy(issuer, quStakeAmount);
+ QSWAP::AddLiqudity_input addLiqInput = {
+ issuer,
+ assetName,
+ assetStakeAmount,
+ 0,
+ 0
+ };
+
+ QSWAP::AddLiqudity_output output = qswap.addLiqudity(issuer, addLiqInput, inputValue);
+ // actually, 141421 liqudity add to the pool, but the first 1000 liqudity is retainedd by the pool rather than the staker
+ EXPECT_EQ(output.userIncreaseLiqudity, 140421);
+ EXPECT_EQ(output.quAmount, 200*1000);
+ EXPECT_EQ(output.assetAmount, 100*1000);
+
+ QSWAP::GetPoolBasicState_output output2 = qswap.getPoolBasicState(issuer, assetName);
+ EXPECT_EQ(output2.poolExists, true);
+ EXPECT_EQ(output2.reservedQuAmount, 200*1000);
+ EXPECT_EQ(output2.reservedAssetAmount, 100*1000);
+ EXPECT_EQ(output2.totalLiqudity, 141421);
+ // printf("pool state: %lld, %lld, %lld\n", output2.reservedQuAmount, output2.reservedAssetAmount, output2.totalLiqudity);
+
+ QSWAP::GetLiqudityOf_input getLiqInput = {
+ issuer,
+ assetName,
+ issuer
+ };
+ QSWAP::GetLiqudityOf_output getLiqOutput = qswap.getLiqudityOf(getLiqInput);
+ EXPECT_EQ(getLiqOutput.liqudity, 140421);
+
+ // 2. add liqudity second time
+ increaseEnergy(issuer, quStakeAmount);
+ addLiqInput = {
+ issuer,
+ assetName,
+ assetStakeAmount,
+ 0,
+ 0
+ };
+
+ QSWAP::AddLiqudity_output output3 = qswap.addLiqudity(issuer, addLiqInput, inputValue);
+ EXPECT_EQ(output3.userIncreaseLiqudity, 141421);
+ EXPECT_EQ(output3.quAmount, 200*1000);
+ EXPECT_EQ(output3.assetAmount, 100*1000);
+
+ getLiqOutput = qswap.getLiqudityOf(getLiqInput);
+ EXPECT_EQ(getLiqOutput.liqudity, 281842); // 140421 + 141421
+
+ QSWAP::RemoveLiqudity_input rmLiqInput = {
+ issuer,
+ assetName,
+ 141421,
+ 200*1000, // should lte 1000*200
+ 100*1000, // should lte 1000*100
+ };
+
+ // 3. remove liqudity
+ QSWAP::RemoveLiqudity_output rmLiqOutput = qswap.removeLiqudity(issuer, rmLiqInput, 0);
+ // printf("qu: %lld, asset: %lld\n", rmLiqOutput.quAmount, rmLiqOutput.assetAmount);
+ EXPECT_EQ(rmLiqOutput.quAmount, 1000 * 200);
+ EXPECT_EQ(rmLiqOutput.assetAmount, 1000 * 100);
+
+ getLiqOutput = qswap.getLiqudityOf(getLiqInput);
+ // printf("liq: %lld\n", getLiqOutput.liqudity);
+ EXPECT_EQ(getLiqOutput.liqudity, 140421); // 281842 - 141421
+ }
+}
+
+// failed case
+TEST(ContractSwap, LiqTest2)
+{
+ ContractTestingQswap qswap;
+
+ id issuer(1, 2, 3, 4);
+ uint64 assetName = assetNameFromString("QSWAP0");
+ uint64 invalidAssetName = assetNameFromString("QSWAP1");
+ sint64 numberOfShares = 1000*1000;
+
+ // 0. issue an asset and create a pool
+ {
+ increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE);
+ QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 };
+ EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares);
+ EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares);
+
+ increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE);
+ EXPECT_TRUE(qswap.createPool(issuer, assetName));
+ }
+
+ // add liqudity to invalid pool,
+ {
+ // decreaseEnergy(getBalance(issuer));
+ uint64 quAmount = 1000;
+ increaseEnergy(issuer, quAmount);
+ QSWAP::AddLiqudity_input addLiqInput = {
+ issuer,
+ invalidAssetName,
+ 1000,
+ 0,
+ 0
+ };
+
+ QSWAP::AddLiqudity_output output = qswap.addLiqudity(issuer, addLiqInput, 1000);
+ EXPECT_EQ(output.userIncreaseLiqudity, 0);
+ EXPECT_EQ(output.quAmount, 0);
+ EXPECT_EQ(output.assetAmount, 0);
+ }
+
+ // add liqudity with asset more than holdings
+ {
+ increaseEnergy(issuer, 1000);
+ QSWAP::AddLiqudity_input addLiqInput = {
+ issuer,
+ assetName,
+ 1000*1000 + 100, // excced 1000*1000
+ 0,
+ 0
+ };
+
+ QSWAP::AddLiqudity_output output = qswap.addLiqudity(issuer, addLiqInput, 1000);
+ EXPECT_EQ(output.userIncreaseLiqudity, 0);
+ EXPECT_EQ(output.quAmount, 0);
+ EXPECT_EQ(output.assetAmount, 0);
+ }
+}
diff --git a/test/test.vcxproj b/test/test.vcxproj
index 06652270..a9002794 100644
--- a/test/test.vcxproj
+++ b/test/test.vcxproj
@@ -110,8 +110,11 @@
-
+
+
+
+
@@ -123,7 +126,6 @@
-
diff --git a/test/test.vcxproj.filters b/test/test.vcxproj.filters
index 9dd1d057..ecf65a20 100644
--- a/test/test.vcxproj.filters
+++ b/test/test.vcxproj.filters
@@ -19,6 +19,7 @@
+
diff --git a/test/uint128.cpp b/test/uint128.cpp
new file mode 100644
index 00000000..a2b273e5
--- /dev/null
+++ b/test/uint128.cpp
@@ -0,0 +1,199 @@
+// Copyright (c) 2013 - 2018 Jason Lee @ calccrypto at gmail.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+// https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp
+
+#define NO_UEFI
+
+#include "gtest/gtest.h"
+#include "../src/platform/uint128.h"
+
+
+TEST(Uint128Arithmetic, add){
+ uint128_t low (0, 1);
+ uint128_t high(1, 0);
+
+ EXPECT_EQ(low + low, uint128_t(0, 2));
+ EXPECT_EQ(low + high, uint128_t(1, 1));
+ EXPECT_EQ(high + high, uint128_t(2, 0));
+
+ EXPECT_EQ(low += low, uint128_t(0, 2));
+ EXPECT_EQ(low += high, uint128_t(1, 2));
+ EXPECT_EQ(high += low, uint128_t(2, 2));
+}
+
+// https://github.com/calccrypto/uint128_t/blob/master/tests/testcases/sub.cpp
+TEST(Uint128Arithmetic, subtract){
+ uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL);
+ uint128_t small(1);
+
+ EXPECT_EQ(small - small, uint128_t(0, 0));
+ EXPECT_EQ(small - big, uint128_t(0, 2));
+ EXPECT_EQ(big - small, uint128_t(0xffffffffffffffffULL, 0xfffffffffffffffeULL));
+ EXPECT_EQ(big - big, uint128_t(0, 0));
+}
+
+// https://github.com/calccrypto/uint128_t/blob/master/tests/testcases/div.cpp
+TEST(Uint128Arithmetic, divide){
+ const uint128_t big_val (0xfedbca9876543210ULL);
+ const uint128_t small_val(0xffffULL);
+ const uint128_t res_val (0xfedcc9753fc9ULL);
+
+ EXPECT_EQ(small_val / small_val, uint128_t(0, 1));
+ EXPECT_EQ(small_val / big_val, uint128_t(0, 0));
+
+ EXPECT_EQ(big_val / big_val, uint128_t(0, 1));
+
+ // EXPECT_THROW(uint128_t(1) / uint128_t(0), std::domain_error);
+}
+
+TEST(Uint128Arithmetic, multiply){
+ uint128_t val(0xfedbca9876543210ULL);
+
+ EXPECT_EQ(val * val, uint128_t(0xfdb8e2bacbfe7cefULL, 0x010e6cd7a44a4100ULL));
+
+ const uint128_t zero = 0;
+ EXPECT_EQ(val * zero, zero);
+ EXPECT_EQ(zero * val, zero);
+
+ const uint128_t one = 1;
+ EXPECT_EQ(val * one, val);
+ EXPECT_EQ(one * val, val);
+}
+
+TEST(Uint128Comparison, equals){
+ EXPECT_EQ( (uint128_t(0xdeadbeefULL) == uint128_t(0xdeadbeefULL)), true);
+ EXPECT_EQ(!(uint128_t(0xdeadbeefULL) == uint128_t(0xfee1baadULL)), true);
+}
+
+TEST(Uint128Comparison, less_than){
+ const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL);
+ const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL);
+
+ EXPECT_EQ(small < small, false);
+ EXPECT_EQ(small < big, true);
+
+ EXPECT_EQ(big < small, false);
+ EXPECT_EQ(big < big, false);
+}
+
+
+TEST(Uint128Comparison, greater_than){
+ const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL);
+ const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL);
+
+ EXPECT_EQ(small > small, false);
+ EXPECT_EQ(small > big, false);
+
+ EXPECT_EQ(big > small, true);
+ EXPECT_EQ(big > big, false);
+}
+
+TEST(Uint128Comparison, greater_than_or_equals){
+ const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL);
+ const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL);
+
+ EXPECT_EQ(small >= small, true);
+ EXPECT_EQ(small >= big, false);
+
+ EXPECT_EQ(big >= small, true);
+ EXPECT_EQ(big >= big, true);
+}
+
+TEST(Uint128Comparison, less_than_or_equals){
+ const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL);
+ const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL);
+
+ EXPECT_EQ(small <= small, true);
+ EXPECT_EQ(small <= big, true);
+
+ EXPECT_EQ(big <= small, false);
+ EXPECT_EQ(big <= big, true);
+}
+
+TEST(Uint128BitShift, left){
+ // operator<<
+ uint128_t val(0x1);
+ uint64_t exp_val = 1;
+ for(uint8_t i = 0; i < 64; i++){
+ EXPECT_EQ(val << uint128_t(i), uint128_t(exp_val << i));
+ }
+
+ uint128_t zero(0);
+ for(uint8_t i = 0; i < 64; i++){
+ EXPECT_EQ(zero << uint128_t(i), uint128_t(0));
+ }
+
+ // operator<<=
+ for(uint8_t i = 0; i < 63; i++){ // 1 is already a bit
+ EXPECT_EQ(val <<= uint128_t(1), uint128_t(exp_val <<= 1));
+ }
+
+ for(uint8_t i = 0; i < 63; i++){
+ EXPECT_EQ(zero <<= uint128_t(1), uint128_t(0));
+ }
+}
+
+TEST(Uint128BitShift, right){
+ // operator>>
+ uint128_t val(0xffffffffffffffffULL);
+ uint64_t exp = 0xffffffffffffffffULL;
+ for(uint8_t i = 0; i < 64; i++){
+ EXPECT_EQ(val >> uint128_t(i), uint128_t(exp >> i));
+ }
+
+ uint128_t zero(0);
+ for(uint8_t i = 0; i < 64; i++){
+ EXPECT_EQ(zero >> uint128_t(i), uint128_t(0));
+ }
+
+ // operator>>=
+ for(uint8_t i = 0; i < 64; i++){
+ EXPECT_EQ(val >>= uint128_t(1), uint128_t(exp >>= 1));
+ }
+
+ for(uint8_t i = 0; i < 64; i++){
+ EXPECT_EQ(zero >>= uint128_t(1), uint128_t(0));
+ }
+}
+
+TEST(Uint128BitWise, and){
+ uint128_t t ((bool) true);
+ uint128_t f ((bool) false);
+ uint128_t u8 ((uint8_t) 0xaaULL);
+ uint128_t u16((uint16_t) 0xaaaaULL);
+ uint128_t u32((uint32_t) 0xaaaaaaaaULL);
+ uint128_t u64((uint64_t) 0xaaaaaaaaaaaaaaaaULL);
+
+ const uint128_t val(0xf0f0f0f0f0f0f0f0ULL, 0xf0f0f0f0f0f0f0f0ULL);
+
+ EXPECT_EQ(t & val, uint128_t(0));
+ EXPECT_EQ(f & val, uint128_t(0));
+ EXPECT_EQ(u8 & val, uint128_t(0xa0ULL));
+ EXPECT_EQ(u16 & val, uint128_t(0xa0a0ULL));
+ EXPECT_EQ(u32 & val, uint128_t(0xa0a0a0a0ULL));
+ EXPECT_EQ(u64 & val, uint128_t(0xa0a0a0a0a0a0a0a0ULL));
+
+ EXPECT_EQ(t &= val, uint128_t(0x0ULL));
+ EXPECT_EQ(f &= val, uint128_t(0x0ULL));
+ EXPECT_EQ(u8 &= val, uint128_t(0xa0ULL));
+ EXPECT_EQ(u16 &= val, uint128_t(0xa0a0ULL));
+ EXPECT_EQ(u32 &= val, uint128_t(0xa0a0a0a0ULL));
+ EXPECT_EQ(u64 &= val, uint128_t(0xa0a0a0a0a0a0a0a0ULL));
+}
\ No newline at end of file