Asset 資產模組
Asset 資產模組

Asset 資產模組

Tags
Golang
System Design
Maching System
Date
Mar 31, 2024
notion image
用戶資產除了基本的UserID、AssetID、Available(可用資產),還需Frozen(凍結資產)欄位,以實現用戶下單時把下單資金凍結。
type UserAsset struct { UserID int AssetID int Available decimal.Decimal Frozen decimal.Decimal }
一個交易對會有多個用戶,並且每個用戶都會有多個資產,如果資產都需存在memory當中,可以用兩個hash table來實作asset repository。
由於在撮合時也有機會透過API讀取memory的用戶資產,所以須用read-write-lock保護。
type assetRepo struct { usersAssetsMap map[int]map[int]*domain.UserAsset lock *sync.RWMutex // another code... }
用戶資產的use case主要有轉帳Transfer()、凍結Freeze()、解凍Unfreeze(),另外我們還需liability user(負債帳戶),他代表整個系統實際的資產,也可用來對帳,負債帳戶透過LiabilityUserTransfer()將資產從系統轉給用戶。
type UserAssetUseCase interface { LiabilityUserTransfer(ctx context.Context, toUserID, assetID int, amount decimal.Decimal) (*TransferResult, error) TransferFrozenToAvailable(ctx context.Context, fromUserID, toUserID int, assetID int, amount decimal.Decimal) (*TransferResult, error) TransferAvailableToAvailable(ctx context.Context, fromUserID, toUserID int, assetID int, amount decimal.Decimal) (*TransferResult, error) Freeze(ctx context.Context, userID, assetID int, amount decimal.Decimal) (*TransferResult, error) Unfreeze(ctx context.Context, userID, assetID int, amount decimal.Decimal) (*TransferResult, error) // ..another functions }
如果liability user id為1、用戶A user id為100、btc id為1、usdt id為2,其他用戶在系統已存入10btc與100000usdt。
此時用戶A對系統deposit儲值100000 usdt,並下了1顆btc價格為62000usdt的買單,此時需凍結62000usdt,將資產直接展開成一個二維表顯示如下:
user id
asset id
available
frozen
1
1
-10
0
1
2
-200000
0
100
1
0
0
100
2
38000
62000
其他用戶...
在deposit時會呼叫LiabilityUserTransfer()assetRepo呼叫GetAssetWithInit(),如果用戶資產存在則返回,不存在則初始化創建,資產模組在需要使用用戶資產時才創建他,不需先預載用戶資料表,也因為如此,在進入撮合系統前的auth非常重要,必須是認證過的用戶才能進入資產模組。
liability user呼叫SubAssetAvailable()減少資產,用戶呼叫AddAssetAvailable()獲取資產,資產的變動會呼叫transferResult.addUserAsset()添加至transferResult,最後回應給下游服務。
func (u *userAsset) LiabilityUserTransfer(ctx context.Context, toUserID, assetID int, amount decimal.Decimal) (*domain.TransferResult, error) { if amount.LessThanOrEqual(decimal.Zero) { return nil, errors.New("can not operate less or equal zero amount") } transferResult := createTransferResult() liabilityUserAsset, err := u.assetRepo.GetAssetWithInit(domain.LiabilityUserID, assetID) if err != nil { return nil, errors.Wrap(err, "get asset with init failed") } toUserAsset, err := u.assetRepo.GetAssetWithInit(toUserID, assetID) if err != nil { return nil, errors.Wrap(err, "get asset with init failed") } u.assetRepo.SubAssetAvailable(liabilityUserAsset, amount) u.assetRepo.AddAssetAvailable(toUserAsset, amount) transferResult.addUserAsset(liabilityUserAsset) transferResult.addUserAsset(toUserAsset) return transferResult.TransferResult, nil }
其他的use case都與LiabilityUserTransfer()類似,只是資產的轉移有差異,最後都會透過transferResult將變動的資產回應給下游服務。
在創建訂單時呼叫Freeze(),先檢查用戶是否有足夠資產userAsset.Available.Cmp(amount) < 0,如果足夠則呼叫SubAssetAvailable()減少資產,並呼叫AddAssetFrozen()增加凍結資產。
func (u *userAsset) Freeze(ctx context.Context, userID, assetID int, amount decimal.Decimal) (*domain.TransferResult, error) { // another code... if userAsset.Available.Cmp(amount) < 0 { return nil, errors.Wrap(domain.LessAmountErr, "less amount err") } u.assetRepo.SubAssetAvailable(userAsset, amount) u.assetRepo.AddAssetFrozen(userAsset, amount) transferResult.addUserAsset(userAsset) return transferResult.TransferResult, nil }
在撮合訂單時呼叫TransferFrozenToAvailable(),先檢查用戶(fromUser)是否有足夠的凍結資產後呼叫SubAssetFrozen()減少凍結用戶(fromUser)資產,並呼叫AddAssetAvailable()增加用戶(toUser)資產。
func (u *userAsset) TransferFrozenToAvailable(ctx context.Context, fromUserID, toUserID, assetID int, amount decimal.Decimal) (*domain.TransferResult, error) { // another code... if fromUserAsset.Frozen.Cmp(amount) < 0 { return nil, errors.Wrap(domain.LessAmountErr, "less amount err") } u.assetRepo.SubAssetFrozen(fromUserAsset, amount) u.assetRepo.AddAssetAvailable(toUserAsset, amount) transferResult.addUserAsset(fromUserAsset) transferResult.addUserAsset(toUserAsset) return transferResult.TransferResult, nil }
在用戶轉帳時呼叫TransferAvailableToAvailable(),檢查用戶(fromUser)是否有足夠資產後呼叫SubAssetAvailable()減少資產並呼叫AddAssetAvailable()增加用戶(toUser)資產。
func (u *userAsset) TransferAvailableToAvailable(ctx context.Context, fromUserID, toUserID, assetID int, amount decimal.Decimal) (*domain.TransferResult, error) { // another code... if fromUserAsset.Available.Cmp(amount) < 0 { return nil, errors.Wrap(domain.LessAmountErr, "less amount err") } u.assetRepo.SubAssetAvailable(fromUserAsset, amount) u.assetRepo.AddAssetAvailable(toUserAsset, amount) transferResult.addUserAsset(fromUserAsset) transferResult.addUserAsset(toUserAsset) return transferResult.TransferResult, nil }