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 }