Swap Contract
module moveview::Swap {
// Import required modules and libraries
use aptos_framework::coin::{Self, Coin, MintCapability, BurnCapability};
use std::string;
use std::string::String;
use std::option;
use moveview::Math::{sqrt, min};
use std::signer::address_of;
use moveview::Math;
// Constant for minimum liquidity required in LP tokens
const MINIMUM_LIQUIDITY: u64 = 1000;
// Define a phantom struct LP with generic types X and Y to represent LP tokens for token pair X-Y
struct LP<phantom X, phantom Y> {}
// Define a struct Pair with generic types X and Y representing a token pair with its related data
struct Pair<phantom X, phantom Y> has key {
x_coin: Coin<X>,
y_coin: Coin<Y>,
lp_locked: Coin<LP<X, Y>>,
lp_mint: MintCapability<LP<X, Y>>,
lp_burn: BurnCapability<LP<X, Y>>,
}
// Function to generate LP token name symbol for a given token pair X-Y
public fun generate_lp_name_symbol<X, Y>(): String {
let lp_name_symbol = string::utf8(b"");
string::append_utf8(&mut lp_name_symbol, b"LP");
string::append_utf8(&mut lp_name_symbol, b"-");
string::append(&mut lp_name_symbol, coin::symbol<X>());
string::append_utf8(&mut lp_name_symbol, b"-");
string::append(&mut lp_name_symbol, coin::symbol<Y>());
lp_name_symbol
}
// Function to create a new pool for token pair X-Y
public entry fun create_pool<X, Y>(sender: &signer) {
// Check if the pair already exists (a pair for X-Y or Y-X)
assert!(!pair_exist<X, Y>(@moveview), 1000);
// Generate LP token name and symbol for the token pair X-Y
let lp_name_symbol = generate_lp_name_symbol<X, Y>();
// Initialize LP token and get its mint and burn capabilities
let (lp_burn, lp_freeze, lp_mint) = coin::initialize<LP<X, Y>>(
sender,
lp_name_symbol,
lp_name_symbol,
6, // Number of decimal places for the LP token
true, // LP token is fungible
);
// Remove the freeze capability to prevent freezing LP tokens
coin::destroy_freeze_cap(lp_freeze);
// Create a new Pair for token pair X-Y and store it in the global storage
move_to(
sender,
Pair<X, Y> {
x_coin: coin::zero<X>(),
y_coin: coin::zero<Y>(),
lp_locked: coin::zero<LP<X, Y>>(),
lp_mint,
lp_burn,
},
);
}
// Function to add liquidity for a given token pair X-Y
public entry fun add_liquidity<X, Y>(sender: &signer, x_amount: u64, y_amount: u64)
acquires Pair
{
// Make sure the pair exists
assert!(exists<Pair<X, Y>>(@moveview), 1000);
// Borrow the Pair data from global storage
let pair = borrow_global_mut<Pair<X, Y>>(@moveview);
// Convert the amounts to u128 to prevent overflow during calculations
let x_amount = (x_amount as u128);
let y_amount = (y_amount as u128);
// Get the current reserves for token X and Y
let x_reserve = (coin::value(&pair.x_coin) as u128);
let y_reserve = (coin::value(&pair.y_coin) as u128);
// Calculate the optimal amount of Y to be added given the amount of X
let y_amount_optimal = quote(x_amount, x_reserve, y_reserve);
// Choose the smaller of the actual Y amount and the optimal Y amount
if (y_amount_optimal <= y_amount) {
y_amount = y_amount_optimal;
}else{
let x_amount_optimal = quote(y_amount,y_reserve,x_reserve);
x_amount = x_amount_optimal;
};
// Withdraw X and Y tokens from the sender's account
let x_amount_coin = coin::withdraw<X>(sender, (x_amount as u64));
let y_amount_coin = coin::withdraw<Y>(sender, (y_amount as u64));
// Deposit the withdrawn tokens into the Pair
coin::merge(&mut pair.x_coin, x_amount_coin);
coin::merge(&mut pair.y_coin, y_amount_coin);
// Calculate the liquidity to be minted and mint LP tokens accordingly
let liquidity;
let total_supply = *option::borrow(&coin::supply<LP<X, Y>>());
if (total_supply == 0){
liquidity = sqrt(((x_amount * y_amount) as u128)) - MINIMUM_LIQUIDITY;
let lp_locked = coin::mint(MINIMUM_LIQUIDITY, &pair.lp_mint);
coin::merge(&mut pair.lp_locked, lp_locked);
} else {
liquidity = (min(
Math::mul_div(x_amount, total_supply, x_reserve),
Math::mul_div(y_amount, total_supply, y_reserve),
) as u64);
};
// Mint the liquidity and deposit it into the sender's account
let lp_coin = coin::mint<LP<X, Y>>(liquidity, &pair.lp_mint);
let addr = address_of(sender);
if (!coin::is_account_registered<LP<X, Y>>(addr)) {
coin::register<LP<X, Y>>(sender);
};
coin::deposit(addr, lp_coin);
}
// Function to remove liquidity for a given token pair X-Y
public entry fun remove_liquidity<X, Y>(sender: &signer, liquidity: u64) acquires Pair {
// Make sure the pair exists
assert!(exists<Pair<X, Y>>(@moveview), 1000);
// Borrow the Pair data from global storage
let pair = borrow_global_mut<Pair<X, Y>>(@moveview);
// Withdraw LP tokens from the sender's account
let liquidity_coin = coin::withdraw<LP<X, Y>>(sender, liquidity);
coin::burn(liquidity_coin, &pair.lp_burn);
// Get the total supply of LP tokens, and the current reserves for token X and Y
let total_supply = *option::borrow(&coin::supply<LP<X,Y>>());
let x_reserve = (coin::value(&pair.x_coin) as u128);
let y_reserve = (coin::value(&pair.y_coin) as u128);
// Calculate the amounts of X and Y to be removed based on the liquidity
let x_amount = Math::mul_div((liquidity as u128), x_reserve, total_supply);
let y_amount = Math::mul_div((liquidity as u128), y_reserve, total_supply);
// Extract the amounts of X and Y tokens from the Pair
let x_amount_coin = coin::extract<X>(&mut pair.x_coin,( x_amount as u64));
let y_amount_coin = coin::extract<Y>(&mut pair.y_coin,( y_amount as u64));
// Deposit the extracted tokens into the sender's account
coin::deposit(address_of(sender), x_amount_coin);
coin::deposit(address_of(sender), y_amount_coin);
}
// Function to swap token X for token Y
public entry fun swap_x_to_y<X, Y>(sender: &signer, amount_in: u64) acquires Pair {
// Make sure the pair exists
assert!(exists<Pair<X, Y>>(@moveview), 1000);
// Borrow the Pair data from global storage
let pair = borrow_global_mut<Pair<X, Y>>(@moveview);
// Withdraw the input token X from the sender's account
let coin_in = coin::withdraw<X>(sender, amount_in);
// Register the sender's account for token Y if not already registered
if (!coin::is_account_registered<Y>(address_of(sender))) {
coin::register<Y>(sender);
};
// Get the current reserves for token X and Y
let in_reserve = (coin::value(&pair.x_coin) as u128);
let out_reserve = (coin::value(&pair.y_coin) as u128);
// Calculate the amount of token Y to be received for the given amount of X
let amount_out = get_amount_out((amount_in as u128), in_reserve, out_reserve);
// Merge the input token X into the Pair and extract the output token Y from the Pair
coin::merge(&mut pair.x_coin, coin_in);
let amount_out_coin = coin::extract(&mut pair.y_coin, (amount_out as u64));
// Deposit the received token Y into the sender's account
coin::deposit(address_of(sender), amount_out_coin);
}
// Function to check if a Pair exists for the token pair X-Y or Y-X
public fun pair_exist<X, Y>(addr: address): bool {
exists<Pair<X, Y>>(addr) || exists<Pair<Y, X>>(addr)
}
// Function to calculate the optimal amount of Y given X, X reserve, and Y reserve
public fun quote(x_amount:u128,x_reserve:u128,y_reserve:u128):u128{
Math::mul_div(x_amount,y_reserve,x_reserve)
}
// Function to calculate the amount of output token for a given amount of input token
public fun get_amount_out(amount_in:u128,in_reserve:u128,out_reserve:u128):u128{
let amount_in_with_fee = amount_in * 997;
let numerator = amount_in_with_fee * out_reserve;
let denominator = in_reserve * 1000 + amount_in_with_fee;
numerator / denominator
}
// Function to get the current reserves of token X and Y in the Pair
public fun get_coin<X,Y>():(u64,u64) acquires Pair {
let pair = borrow_global<Pair<X,Y>>(@moveview);
(coin::value(&pair.x_coin),coin::value(&pair.y_coin))
}
}
Tổng quan
Swap Contract là một smart contract được viết bằng Move language, triển khai trên nền tảng Aptos. Contract này cung cấp các chức năng cơ bản của một sàn giao dịch phi tập trung (DEX), cho phép người dùng tạo pool thanh khoản, thêm và rút thanh khoản, cũng như thực hiện các giao dịch swap token.
Cấu trúc dữ liệu
LP <phantom X phantom Y>
Đại diện cho token LP (Liquidity Provider) của cặp token X-Y.
Pair <phantom X phantom Y>
Lưu trữ thông tin về một cặp token, bao gồm:
x_coin
: Số lượng token X trong pooly_coin
: Số lượng token Y trong poollp_locked
: Token LP bị khóalp_mint
: Khả năng tạo mới token LPlp_burn
: Khả năng đốt token LP
Hằng số
MINIMUM_LIQUIDITY
: Số lượng tối thiểu của token LP cần được khóa trong pool (1000).
Chức năng chính
-
create_pool<X Y>
Tạo một pool mới cho cặp token X-Y. -
add_liquidity<X Y>
Thêm thanh khoản vào pool X-Y. -
remove_liquidity<X Y>
Rút thanh khoản từ pool X-Y. -
swap_x_to_y<X Y>
Thực hiện giao dịch swap từ token X sang token Y.
Hàm hỗ trợ
-
generate_lp_name_symbol<X Y>
Tạo tên và ký hiệu cho token LP của cặp X-Y. -
pair_exist<X Y>
Kiểm tra xem pool cho cặp token X-Y đã tồn tại chưa. -
quote
Tính toán số lượng token Y tối ưu dựa trên số lượng token X và dự trữ hiện tại. -
get_amount_out
Tính toán số lượng token đầu ra dựa trên số lượng token đầu vào và dự trữ hiện tại. -
get_coin<X Y>
Lấy thông tin về số lượng token X và Y hiện có trong pool.
Cách sử dụng
- Tạo pool: Gọi
create_pool<X Y>
để tạo một pool mới cho cặp token X-Y. - Thêm thanh khoản: Sử dụng
add_liquidity<X Y>
để thêm token X và Y vào pool. - Swap token: Gọi
swap_x_to_y<X Y>
để đổi token X lấy token Y. - Rút thanh khoản: Sử dụng
remove_liquidity<X Y>
để rút token từ pool.
Lưu ý quan trọng
- Contract sử dụng module Math để thực hiện các phép tính an toàn và chính xác.
- Phí giao dịch được tính trực tiếp trong hàm
get_amount_out
(0.3%). - Khi tạo pool lần đầu, một lượng nhỏ token LP sẽ bị khóa vĩnh viễn để tránh lỗi chia cho 0.
Bảo mật
- Contract sử dụng các assertion để đảm bảo tính hợp lệ của các thao tác.
- Các phép tính được thực hiện với độ chính xác cao để tránh lỗi làm tròn và tràn số.