Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sport-goals-tracker #27

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions craun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Sport goals tracker CRAUN.

_Приложение для отслеживания спортивных целей с системой штрафов._

Blockchain часть приложения состоит из 2 dApps:
* craunDapp - следит за выполнением целей и управляет балансом пользователя
* goalDapp - хранит конфигурацию цели, текущий статус, количество проваленных попыток и записи фактов исполнения цели

Как это работает?
1. Создается цель
1. Пользователь настраивает цель (Например бег 2км. каждые пн., ср., пт., 5 попыток и стоимость пропуска и т.д.)
2. Создается новый аккаунт цели и затем скриптуется.
3. Записать пользовательские данные в аккаунт цели через функцию «setup».
2. Отслеживание выполнения цели
1. Так как одной из задач craunDapp является управление балансом пользователя, то после создания цели, нужно заморозить баланс пользователя на сумму цели (как вычисляется сумма цели см. ниже).
2. В craunDapp, раз в день проверять статус выполнения цели и принимать решение о снятии средств из замороженного баланса.

Методы goalDapp:
* func setup(userAddress, title, distance, etc.) - записать данные цели в goalDapp
* func record(recordDate: String, recordDistance: Int) - валидирует выполнение цели и меняет статус цели.

Методы craunDapp:
* func deposit() - позволяет пополнить баланс пользователя
* func addGoal(goalAccountAddressStr: String) - замораживает средства пользователя исходя из стоимости цели (колличесто попыток * стоимость 1 провала + стоимость удаления). После этого, для аккаунта пользователя создается 3 баланса для хранения замороженных, активных и потерянных средств.
* func checkUserGoal(goalAccountAddressStr, userAccountAddressStr, recordDate) - проверяет исполнение цели на определенную дату, проверяет статус цели и принимает штрафовать ли пользователя.

_P.S._
_В будущем можно создать мобильное приложение, которое в атоматическом режиме может фиксировать факты испольнения различных целей с использованием GPS, CV и тд:_
* бег
* подтягивания на турнике
* отжимания
* приседания
* скручивания на пресс
* и др.
179 changes: 179 additions & 0 deletions craun/craunApp.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
{-# STDLIB_VERSION 3 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}

func isSubstrExist(str: String, substr: String) = match indexOf(str, substr) {
case index: Int => true
case index: Unit => false
}

func getIntegerFromAccount(accountAddress: Address | Alias, key: String) = match getInteger(accountAddress, key) {
case a: Int => a
case _ => 0
}

func getUserBalanceKey(userAccountAddressStr: String, balanceType: String) = {
userAccountAddressStr + "_" + balanceType + "-balance"
}

@Callable(i)
func deposit() = {
let payment = extract(i.payment)

if (isDefined(payment.assetId)) then {
throw("you can deposit only waves")
} else {
let userAccountAddress = toBase58String(i.caller.bytes)

let userAvailableBalanceKey = getUserBalanceKey(userAccountAddress, "available")

let userBalance = getIntegerFromAccount(this, userAvailableBalanceKey)

let newUserActiveBalance = userBalance + payment.amount

WriteSet([
DataEntry(userAvailableBalanceKey, newUserActiveBalance)
])
}
}

@Callable(i)
func withdraw(amount: Int) = {
let userAccountAddressStr = toBase58String(i.caller.bytes)
let userAccountAddress = addressFromStringValue(userAccountAddressStr)

let userAvailableBalanceKey = getUserBalanceKey(userAccountAddressStr, "available")

let userActiveBalance = getIntegerFromAccount(this, userAvailableBalanceKey)

let newUserActiveBalance = userActiveBalance - amount

if (amount < 0) then {
throw("Can't withdraw negative amount")
} else if (newUserActiveBalance < 0) then {
throw("Not enough balance")
} else {
ScriptResult(
WriteSet([DataEntry(userAvailableBalanceKey, newUserActiveBalance)]),
TransferSet([ScriptTransfer(userAccountAddress, amount, unit)])
)
}
}

@Callable(i)
func addGoal(
goalAccountAddressStr: String
) = {
let userAccountAddress = toBase58String(i.caller.bytes)

let goalAccountAddress = addressFromStringValue(goalAccountAddressStr);

let userAvailableBalanceKey = getUserBalanceKey(userAccountAddress, "available")
let userFrozenBalanceKey = getUserBalanceKey(userAccountAddress, "frozen")
let userLostBalanceKey = getUserBalanceKey(userAccountAddress, "lost")

let userActiveBalance = getIntegerFromAccount(this, userAvailableBalanceKey)
let userFrozenBalance = getIntegerFromAccount(this, userFrozenBalanceKey)

let failPenalty = getIntegerFromAccount(goalAccountAddress, "failPenalty")
let archievePenalty = getIntegerFromAccount(goalAccountAddress, "archievePenalty")
let attemptsCount = getIntegerFromAccount(goalAccountAddress, "attemptsCount")

let frozeAmount = failPenalty * attemptsCount + archievePenalty

if (frozeAmount > userActiveBalance) then {
throw("You can't create goal, not enough balance on your account to froze")
}
else {
let newUserActiveBalance = userActiveBalance - frozeAmount;
let newUserFrozenBalance = userFrozenBalance + frozeAmount;

WriteSet([
DataEntry(userAvailableBalanceKey, newUserActiveBalance),
DataEntry(userFrozenBalanceKey, newUserFrozenBalance)
])
}
}

@Callable(i)
func checkUserGoal(
goalAccountAddressStr: String,
userAccountAddressStr: String,
recordDate: String
) = {
let goalAccountAddress = addressFromStringValue(goalAccountAddressStr);

let userInactiveGoalsKey = userAccountAddressStr + "_" + "innactive-goals"

let userInactiveGoals = match getString(this, userInactiveGoalsKey) {
case x: Unit => ""
case x: String => x
}

let isGoalInactive = isSubstrExist(userInactiveGoals, goalAccountAddressStr);

if (isGoalInactive) then {
throw("Can't not check user goal execution. The goal is not active.")
} else {
let userAvailableBalanceKey = getUserBalanceKey(userAccountAddressStr, "available")
let userFrozenBalanceKey = getUserBalanceKey(userAccountAddressStr, "frozen")
let userLostBalanceKey = getUserBalanceKey(userAccountAddressStr, "lost")

let userActiveBalance = getIntegerFromAccount(this, userAvailableBalanceKey)
let userFrozenBalance = getIntegerFromAccount(this, userFrozenBalanceKey)
let userLostBalance = getIntegerFromAccount(this, userLostBalanceKey)

let failPenalty = getIntegerFromAccount(goalAccountAddress, "failPenalty")
let archievePenalty = getIntegerFromAccount(goalAccountAddress, "archievePenalty")

let goalStatus = getStringValue(goalAccountAddress, "status")

if (goalStatus == "archived") then {
WriteSet([
DataEntry(userAvailableBalanceKey, userActiveBalance + (userFrozenBalance - archievePenalty)),
DataEntry(userFrozenBalanceKey, 0),
DataEntry(userLostBalanceKey, userLostBalance + archievePenalty),
DataEntry(userInactiveGoalsKey, userInactiveGoals + "," + goalAccountAddressStr)
])
} else if (goalStatus == "failed") then {
WriteSet([
DataEntry(userAvailableBalanceKey, userActiveBalance + archievePenalty),
DataEntry(userFrozenBalanceKey, 0),
DataEntry(userInactiveGoalsKey, userInactiveGoals + "," + goalAccountAddressStr)
])
} else if (goalStatus == "completed") then {
WriteSet([
DataEntry(userAvailableBalanceKey, userActiveBalance + userFrozenBalance),
DataEntry(userFrozenBalanceKey, 0),
DataEntry(userInactiveGoalsKey, userInactiveGoals + "," + goalAccountAddressStr)
])
} else {
let goalRecordKey = "record" + "_" + recordDate

let goalRecordValue = match getBoolean(goalAccountAddress, goalRecordKey) {
case a:Boolean => a
case _ => false
}

if (goalRecordValue == false) then {
let updatedUserFrozenBalance = userFrozenBalance - failPenalty;
let updatedUserLostBalance = userLostBalance + failPenalty;

WriteSet([
DataEntry(userFrozenBalanceKey, updatedUserFrozenBalance),
DataEntry(userLostBalanceKey, updatedUserLostBalance)
])
} else {
WriteSet([
DataEntry(userFrozenBalanceKey, userFrozenBalance),
DataEntry(userLostBalanceKey, userLostBalance)
])
}
}
}
}

@Verifier(tx)
func verify() = {
true
}
89 changes: 89 additions & 0 deletions craun/craunGoal.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{-# STDLIB_VERSION 3 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}

func getIntegerFromAccount(accountAddress: Address | Alias, key: String) = match getInteger(accountAddress, key) {
case a: Int => a
case _ => 0
}

@Callable(i)
func setup(
userAddress: String,
title: String,
distance: Int,
recordWeekDays: String,
archievePenalty: Int,
failPenalty: Int,
attemptsCount: Int,
startDate: String,
endDate: String
) = {
WriteSet([
DataEntry("userAddress", userAddress),
DataEntry("status", "running"), #running | archived | failed | completed
DataEntry("title", title),
DataEntry("distance", distance),
DataEntry("recordWeekDays", recordWeekDays),
DataEntry("archievePenalty", archievePenalty),
DataEntry("failPenalty", failPenalty),
DataEntry("attemptsCount", attemptsCount),
DataEntry("failedAttemptsCount", 0),
DataEntry("startDate", startDate),
DataEntry("endDate", endDate)
])
}
@Callable(i)
func archieveGoal() = {
WriteSet([
DataEntry("status", "archived")
])
}

@Callable(i)
func addRecord(recordDate: String, recordDistance: Int) = {
let recordKey = "record" + "_" + recordDate

let distance = getIntegerFromAccount(this, "distance")
let failPenalty = getIntegerFromAccount(this, "failPenalty")
let archievePenalty = getIntegerFromAccount(this, "archievePenalty")
let recordWeekDays = getIntegerFromAccount(this, "recordWeekDays")
let attemptsCount = getIntegerFromAccount(this, "attemptsCount")
let failedAttemptsCount = getIntegerFromAccount(this, "failedAttemptsCount")

let startDate = getStringValue(this, "startDate")
let endDate = getStringValue(this, "endDate")

if (recordDistance >= distance) then {
if (endDate == recordDate) then {
WriteSet([
DataEntry(recordKey, true),
DataEntry("status", "completed")
])
} else {
WriteSet([
DataEntry(recordKey, true)
])
}
} else {
let newFailedAttemptsCount = failedAttemptsCount + 1;

if (newFailedAttemptsCount == attemptsCount) then {
WriteSet([
DataEntry(recordKey, false),
DataEntry("failedAttemptsCount", newFailedAttemptsCount),
DataEntry("status", "failed")
])
} else {
WriteSet([
DataEntry(recordKey, false),
DataEntry("failedAttemptsCount", newFailedAttemptsCount)
])
}
}
}

@Verifier(tx)
func verify() = {
true
}
Loading