// Contract configuration options
options {
server = server;
exit = 144;
}
contract ConstantProductAMM(
// Asset IDs for the trading pair
bytes32 assetX,
bytes32 assetY,
// LP token asset ID
bytes32 lpTokenId,
// Fee in basis points (e.g., 30 = 0.3%)
int feeRate,
// Server public key
pubkey server
) {
// Helper function to verify the constant product invariant
function verifyConstantProduct(int reserveX, int reserveY, int newReserveX, int newReserveY) internal {
// Apply the fee adjustment for swaps
// K = x * y should remain constant or increase
require(newReserveX * newReserveY >= reserveX * reserveY, "Constant product violated");
}
// Add liquidity to the pool
function addLiquidity(int amountX, int amountY, int mintLPTokens, signature userSig, pubkey user) {
// Get current reserves from the input UTXO
int reserveX = tx.input.current.reserveX; // Simplified access
int reserveY = tx.input.current.reserveY; // Simplified access
int totalLPSupply = tx.input.current.lpSupply; // Simplified access
// Calculate new reserves
int newReserveX = reserveX + amountX;
int newReserveY = reserveY + amountY;
// For initial liquidity provision
if (totalLPSupply == 0) {
// Initial LP tokens = sqrt(amountX * amountY)
require(mintLPTokens * mintLPTokens <= amountX * amountY, "Initial LP tokens too high");
require(mintLPTokens * mintLPTokens >= amountX * amountY - 100, "Initial LP tokens too low");
} else {
// Ensure proportional liquidity provision
int lpTokensForX = (amountX * totalLPSupply) / reserveX;
int lpTokensForY = (amountY * totalLPSupply) / reserveY;
// Take the minimum to ensure proportional deposit
int expectedLPTokens = lpTokensForX < lpTokensForY ? lpTokensForX : lpTokensForY;
require(mintLPTokens <= expectedLPTokens, "LP tokens too high");
require(mintLPTokens >= expectedLPTokens - 100, "LP tokens too low"); // Allow for rounding
}
// Verify the input contains the correct assets
require(tx.inputs[1].asset == assetX, "Input 1 asset mismatch");
require(tx.inputs[1].value >= amountX, "Input 1 insufficient");
require(tx.inputs[2].asset == assetY, "Input 2 asset mismatch");
require(tx.inputs[2].value >= amountY, "Input 2 insufficient");
// Verify the outputs
// Output 0: New AMM state with updated reserves
require(tx.outputs[0].scriptPubKey == tx.input.current.scriptPubKey, "Output 0 script mismatch");
require(tx.outputs[0].reserveX == newReserveX, "Output 0 reserveX mismatch");
require(tx.outputs[0].reserveY == newReserveY, "Output 0 reserveY mismatch");
require(tx.outputs[0].lpSupply == totalLPSupply + mintLPTokens, "Output 0 LP supply mismatch");
// Output 1: LP tokens to user
require(tx.outputs[1].asset == lpTokenId, "Output 1 asset mismatch");
require(tx.outputs[1].value == mintLPTokens, "Output 1 value mismatch");
bytes userScript = new P2PKH(user);
require(tx.outputs[1].scriptPubKey == userScript, "Output 1 not spendable by user");
// Verify user signature
require(checkSig(userSig, user), "Invalid user signature");
}
// Remove liquidity from the pool
function removeLiquidity(int burnLPTokens, int withdrawX, int withdrawY, signature userSig, pubkey user) {
// Get current reserves from the input UTXO
int reserveX = tx.input.current.reserveX; // Simplified access
int reserveY = tx.input.current.reserveY; // Simplified access
int totalLPSupply = tx.input.current.lpSupply; // Simplified access
// Calculate proportional withdrawal amounts
int expectedWithdrawX = (burnLPTokens * reserveX) / totalLPSupply;
int expectedWithdrawY = (burnLPTokens * reserveY) / totalLPSupply;
// Verify withdrawal amounts are correct
require(withdrawX <= expectedWithdrawX, "Withdraw X too high");
require(withdrawX >= expectedWithdrawX - 100, "Withdraw X too low"); // Allow for rounding
require(withdrawY <= expectedWithdrawY, "Withdraw Y too high");
require(withdrawY >= expectedWithdrawY - 100, "Withdraw Y too low"); // Allow for rounding
// Calculate new reserves
int newReserveX = reserveX - withdrawX;
int newReserveY = reserveY - withdrawY;
// Verify the input contains LP tokens
require(tx.inputs[1].asset == lpTokenId, "Input 1 asset mismatch");
require(tx.inputs[1].value >= burnLPTokens, "Input 1 insufficient");
// Verify the outputs
// Output 0: New AMM state with updated reserves
require(tx.outputs[0].scriptPubKey == tx.input.current.scriptPubKey, "Output 0 script mismatch");
require(tx.outputs[0].reserveX == newReserveX, "Output 0 reserveX mismatch");
require(tx.outputs[0].reserveY == newReserveY, "Output 0 reserveY mismatch");
require(tx.outputs[0].lpSupply == totalLPSupply - burnLPTokens, "Output 0 LP supply mismatch");
// Output 1: Asset X to user
require(tx.outputs[1].asset == assetX, "Output 1 asset mismatch");
require(tx.outputs[1].value == withdrawX, "Output 1 value mismatch");
bytes userScript = new P2PKH(user);
require(tx.outputs[1].scriptPubKey == userScript, "Output 1 not spendable by user");
// Output 2: Asset Y to user
require(tx.outputs[2].asset == assetY, "Output 2 asset mismatch");
require(tx.outputs[2].value == withdrawY, "Output 2 value mismatch");
require(tx.outputs[2].scriptPubKey == userScript, "Output 2 not spendable by user");
// Verify user signature
require(checkSig(userSig, user), "Invalid user signature");
}
// Swap asset X for asset Y
function swapXforY(int amountX, int minAmountY, signature userSig, pubkey user) {
// Get current reserves from the input UTXO
int reserveX = tx.input.current.reserveX; // Simplified access
int reserveY = tx.input.current.reserveY; // Simplified access
int totalLPSupply = tx.input.current.lpSupply; // Simplified access
// Calculate the amount out with fee
int amountXWithFee = amountX * (10000 - feeRate) / 10000;
int numerator = amountXWithFee * reserveY;
int denominator = reserveX + amountXWithFee;
int amountYOut = numerator / denominator;
// Verify minimum output amount
require(amountYOut >= minAmountY, "Output amount below minimum");
// Calculate new reserves
int newReserveX = reserveX + amountX;
int newReserveY = reserveY - amountYOut;
// Verify the constant product invariant
verifyConstantProduct(reserveX, reserveY, newReserveX, newReserveY);
// Verify the input contains asset X
require(tx.inputs[1].asset == assetX, "Input 1 asset mismatch");
require(tx.inputs[1].value >= amountX, "Input 1 insufficient");
// Verify the outputs
// Output 0: New AMM state with updated reserves
require(tx.outputs[0].scriptPubKey == tx.input.current.scriptPubKey, "Output 0 script mismatch");
require(tx.outputs[0].reserveX == newReserveX, "Output 0 reserveX mismatch");
require(tx.outputs[0].reserveY == newReserveY, "Output 0 reserveY mismatch");
require(tx.outputs[0].lpSupply == totalLPSupply, "Output 0 LP supply mismatch");
// Output 1: Asset Y to user
require(tx.outputs[1].asset == assetY, "Output 1 asset mismatch");
require(tx.outputs[1].value == amountYOut, "Output 1 value mismatch");
bytes userScript = new P2PKH(user);
require(tx.outputs[1].scriptPubKey == userScript, "Output 1 not spendable by user");
// Verify user signature
require(checkSig(userSig, user), "Invalid user signature");
}
// Swap asset Y for asset X
function swapYforX(int amountY, int minAmountX, signature userSig, pubkey user) {
// Implementation similar to swapXforY, with X and Y reversed
// Omitted for brevity
}
}