Initial implementation of smart-contract
This commit is contained in:
commit
f668685d59
5 changed files with 257 additions and 0 deletions
219
contract.go
Normal file
219
contract.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
package smart_contract_bets
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/lib/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/oracle"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||
)
|
||||
|
||||
// url для оракула:
|
||||
const dataURLKey = "dataURL"
|
||||
|
||||
// контракт, принимает ставки на одно событие:
|
||||
const eventIndexValue = 1
|
||||
|
||||
// значения, получаемые оракулом: 0 (событие не сыграно), 1 (победа первого игрока), 2 (победа второго игрока):
|
||||
const winnerKey = "winner"
|
||||
|
||||
// префикс имён ключей для сохранения в storage ставок игроков
|
||||
const playerKeyPrefix = "bet_"
|
||||
|
||||
type Bet struct {
|
||||
// величина ставки
|
||||
amount int
|
||||
// сторона, на которую сделана ставка (в событии участвует 2 стороны)
|
||||
winner int
|
||||
// флаг, обозначающий что выигрыш получен
|
||||
payed bool
|
||||
}
|
||||
|
||||
func _deploy(data interface{}, isUpdate bool) {
|
||||
if isUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := storage.GetContext()
|
||||
dataURL := "https://my-json-server.typicode.com/okonstantinova/smart-contract-bets/events/"
|
||||
storage.Put(ctx, dataURLKey, dataURL)
|
||||
}
|
||||
|
||||
func PlaceBet(amount int, winner int, player interop.Hash160) {
|
||||
if !runtime.CheckWitness(player) {
|
||||
runtime.Log("Player is not the same as the caller")
|
||||
return
|
||||
}
|
||||
|
||||
if amount <= 0 {
|
||||
runtime.Log("Invalid amount value")
|
||||
return
|
||||
}
|
||||
|
||||
if winner != 1 && winner != 2 {
|
||||
runtime.Log("Invalid winner value")
|
||||
return
|
||||
}
|
||||
|
||||
balance := gas.BalanceOf(player)
|
||||
if balance < amount {
|
||||
runtime.Log("Not enough gas on balance")
|
||||
return
|
||||
}
|
||||
|
||||
ctx := storage.GetContext()
|
||||
if storage.Get(ctx, winnerKey) != nil {
|
||||
runtime.Log("The event has played, cannot place bet")
|
||||
return
|
||||
}
|
||||
|
||||
if storage.Get(ctx, player) != nil {
|
||||
runtime.Log("Player has already placed bet")
|
||||
return
|
||||
}
|
||||
|
||||
contractHash := runtime.GetExecutingScriptHash()
|
||||
result := gas.Transfer(player, contractHash, amount, nil)
|
||||
if !result {
|
||||
runtime.Log("Transfer operation failed")
|
||||
return
|
||||
}
|
||||
|
||||
bet := Bet{
|
||||
amount: amount,
|
||||
winner: winner,
|
||||
payed: false,
|
||||
}
|
||||
|
||||
playerKey := playerKeyPrefix + address.FromHash160(player)
|
||||
storage.Put(ctx, playerKey, std.Serialize(bet))
|
||||
runtime.Log("Player " + address.FromHash160(player) + " placed " + std.Itoa10(amount) +
|
||||
" gas bet on side " + std.Itoa10(winner))
|
||||
}
|
||||
|
||||
// OnNEP17Payment вызывается как callback при успешном переводе газа на счёт контракта
|
||||
func OnNEP17Payment(from interop.Hash160, amount int, data any) {
|
||||
contractHash := runtime.GetExecutingScriptHash()
|
||||
balance := gas.BalanceOf(contractHash)
|
||||
runtime.Log("Contract's balance changed and has " + std.Itoa10(balance) + " gas")
|
||||
}
|
||||
|
||||
func CheckEventStatus() {
|
||||
ctx := storage.GetContext()
|
||||
|
||||
if storage.Get(ctx, winnerKey) != nil {
|
||||
runtime.Log("The event has played, no action required anymore")
|
||||
return
|
||||
}
|
||||
|
||||
dataURL := storage.Get(ctx, dataURLKey).(string) + std.Itoa10(eventIndexValue)
|
||||
oracle.Request(dataURL, nil, "oracleCallback", nil, 2*oracle.MinimumResponseGas)
|
||||
runtime.Log("Oracle requested to check " + dataURL)
|
||||
}
|
||||
|
||||
func OracleCallback(url string, userData any, code int, result []byte) {
|
||||
runtime.Log("Result " + string(result))
|
||||
|
||||
callingHash := runtime.GetCallingScriptHash()
|
||||
if !callingHash.Equals(oracle.Hash) {
|
||||
panic("Not called from the oracle contract")
|
||||
}
|
||||
|
||||
if code != oracle.Success {
|
||||
panic("Request failed for " + url + " with code " + std.Itoa10(code))
|
||||
}
|
||||
|
||||
ctx := storage.GetContext()
|
||||
if storage.Get(ctx, winnerKey) != nil {
|
||||
panic("Event status change is already processed")
|
||||
}
|
||||
|
||||
data := std.JSONDeserialize(result).(map[string]any)
|
||||
winner := data["winner"].(int)
|
||||
|
||||
if winner == 0 {
|
||||
runtime.Log("Oracle reports that the event has not played yet")
|
||||
return
|
||||
}
|
||||
|
||||
if winner != 1 && winner != 2 {
|
||||
panic("Invalid winner value")
|
||||
}
|
||||
|
||||
storage.Put(ctx, winnerKey, winner)
|
||||
runtime.Log("The event winner is set to be " + std.Itoa10(winner))
|
||||
}
|
||||
|
||||
func ClaimWin(player interop.Hash160) bool {
|
||||
if !runtime.CheckWitness(player) {
|
||||
panic("Player is not the same as the caller")
|
||||
}
|
||||
|
||||
ctx := storage.GetContext()
|
||||
winnerValue := storage.Get(ctx, winnerKey)
|
||||
|
||||
if winnerValue == nil {
|
||||
panic("The event has not played yet")
|
||||
}
|
||||
|
||||
winner := winnerValue.(int)
|
||||
|
||||
playerKey := playerKeyPrefix + address.FromHash160(player)
|
||||
data := storage.Get(ctx, playerKey)
|
||||
if data == nil {
|
||||
panic("Player did not place bet")
|
||||
}
|
||||
|
||||
bet := std.Deserialize(data.([]byte)).(Bet)
|
||||
if bet.payed {
|
||||
runtime.Log("Player has already claimed win")
|
||||
return false
|
||||
}
|
||||
|
||||
if bet.winner != winner {
|
||||
runtime.Log("The bet has not won")
|
||||
return false
|
||||
}
|
||||
|
||||
win := CalculateWin(player, bet)
|
||||
contractHash := runtime.GetExecutingScriptHash()
|
||||
result := gas.Transfer(contractHash, player, win, nil)
|
||||
if !result {
|
||||
panic("Transfer operation failed")
|
||||
}
|
||||
|
||||
bet.payed = true
|
||||
storage.Put(ctx, playerKey, std.Serialize(bet))
|
||||
runtime.Log("Player " + address.FromHash160(player) + " claimed their win and received " +
|
||||
std.Itoa10(win) + " gas")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// CalculateWin рассчитывает выигрыш для игрока победителя
|
||||
func CalculateWin(player interop.Hash160, playerBet Bet) int {
|
||||
ctx := storage.GetContext()
|
||||
|
||||
totalWin := 0 // сумма ставок всех победителей
|
||||
totalLost := 0 // сумма ставок всех проигравших
|
||||
|
||||
it := storage.Find(ctx, playerKeyPrefix, storage.ValuesOnly)
|
||||
for iterator.Next(it) {
|
||||
data := iterator.Value(it)
|
||||
bet := std.Deserialize(data.([]byte)).(Bet)
|
||||
|
||||
if bet.winner == playerBet.winner {
|
||||
totalWin += bet.amount
|
||||
} else {
|
||||
totalLost += bet.amount
|
||||
}
|
||||
}
|
||||
// выигрыш состоит из:
|
||||
// 1) изначальной ставки данного победителя
|
||||
// 2) доли победителя от суммы ставок всех проигравших пропорционально его ставке к сумме ставок всех победителей,
|
||||
// поделенной пополам с владельцем контракта
|
||||
return playerBet.amount + int(float64(totalLost)*float64(playerBet.amount)/float64(totalWin))/2
|
||||
}
|
5
contract.yml
Normal file
5
contract.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: "Bet contract"
|
||||
supported standards: []
|
||||
events: []
|
||||
permissions:
|
||||
- methods: ["request", "transfer"]
|
16
data.json
Normal file
16
data.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"events": [
|
||||
{
|
||||
"id": 0,
|
||||
"date": "2023-12-12",
|
||||
"description": "Football: Chelsy & Arsenal",
|
||||
"winner": 1
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"date": "2024-01-07",
|
||||
"description": "Hockey: Avangard Omsk & Ak Bars Kazan",
|
||||
"winner": 0
|
||||
}
|
||||
]
|
||||
}
|
8
go.mod
Normal file
8
go.mod
Normal file
|
@ -0,0 +1,8 @@
|
|||
module contract
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/nspcc-dev/neo-go v0.104.0
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231121104256-0493ddbd70b2
|
||||
)
|
9
go.sum
Normal file
9
go.sum
Normal file
|
@ -0,0 +1,9 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/nspcc-dev/neo-go v0.104.0 h1:FGj3Z46yABcFIAI1SCLd1jQSoh+B00h/2VAgEgY1JKQ=
|
||||
github.com/nspcc-dev/neo-go v0.104.0/go.mod h1:omsUK5PAtG2/nQ3/evs95QEg3wtkj3LH53e0NKtXVwQ=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231121104256-0493ddbd70b2 h1:hPVF8iMmsQ15GSemj1ma6C9BkwfAugEXsUAVTEniK5M=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231121104256-0493ddbd70b2/go.mod h1:J/Mk6+nKeKSW4wygkZQFLQ6SkLOSGX5Ga0RuuuktEag=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
Loading…
Reference in a new issue