Skip to content

Commit

Permalink
Merge pull request #124 from KenshiTech/feat/add-pos-slash
Browse files Browse the repository at this point in the history
Feat: Add slashing mechanism to the client
  • Loading branch information
pouya-eghbali authored Apr 10, 2024
2 parents 2b2dae6 + 4f1a08b commit 551a83e
Show file tree
Hide file tree
Showing 5 changed files with 646 additions and 576 deletions.
971 changes: 396 additions & 575 deletions internal/ethereum/contracts/UnchainedStaking.go

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions internal/pos/eip712.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package pos

import (
"context"
"math/big"

"github.com/KenshiTech/unchained/config"
"github.com/KenshiTech/unchained/ethereum/contracts"
"github.com/KenshiTech/unchained/log"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

func (s *Repository) Slash(address [20]byte, to common.Address, amount *big.Int, nftIds []*big.Int) error {
evmAddress, err := s.posContract.EvmAddressOf(nil, address)

if err != nil {
log.Logger.
With("Error", err).
Error("Failed to get EVM address of the staker")
return err
}

transfer := contracts.UnchainedStakingEIP712Transfer{
From: evmAddress,
To: to,
Amount: amount,
NftIds: nftIds,
}

signature, err := s.eip712Signer.SignTransferRequest(s.evmSigner, &transfer)

if err != nil {
log.Logger.
With("Error", err).
Error("Failed to sign transfer request")
return err
}

tx, err := s.posContract.Transfer(
nil,
[]contracts.UnchainedStakingEIP712Transfer{transfer},
[]contracts.UnchainedStakingSignature{*signature},
)

if err != nil {
log.Logger.
With("Error", err).
Error("Failed to transfer")
return err
}

receipt, err := bind.WaitMined(
context.Background(),
s.ethRPC.Clients[config.App.ProofOfStake.Chain],
tx,
)

if err != nil {
log.Logger.
With("Error", err).
Error("Failed to wait for transaction to be mined")
return err
}

if receipt.Status != types.ReceiptStatusSuccessful {
log.Logger.
With("Error", err).
Error("Transaction failed")
return err
}

log.Logger.
With("Address", evmAddress.Hex()).
With("To", to.Hex()).
With("Amount", amount.String()).
With("NftIds", nftIds).
Info("Slashed")

return nil
}
114 changes: 114 additions & 0 deletions internal/pos/eip712/sign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package eip712

import (
"fmt"
"math/big"

"github.com/KenshiTech/unchained/ethereum"
"github.com/KenshiTech/unchained/ethereum/contracts"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
)

type EIP712Signer struct {
domain apitypes.TypedDataDomain
}

func (s *EIP712Signer) bytesToUnchainedSignature(signature []byte) *contracts.UnchainedStakingSignature {
return &contracts.UnchainedStakingSignature{
V: signature[64],
R: [32]byte(signature[:32]),
S: [32]byte(signature[32:64]),
}
}

func (s *EIP712Signer) signEip712Message(evmSigner *ethereum.EvmSigner, data *apitypes.TypedData) (*contracts.UnchainedStakingSignature, error) {
domainSeparator, err := data.HashStruct("EIP712Domain", data.Domain.Map())
if err != nil {
return nil, err
}

typedDataHash, err := data.HashStruct(data.PrimaryType, data.Message)
if err != nil {
return nil, err
}

message := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
messageHash := crypto.Keccak256(message)

signature, err := crypto.Sign(messageHash, evmSigner.PrivateKey)
if err != nil {
return nil, err
}

if signature[64] < 27 {
signature[64] += 27
}

return s.bytesToUnchainedSignature(signature), nil
}

func (s *EIP712Signer) SignTransferRequest(evmSigner *ethereum.EvmSigner, request *contracts.UnchainedStakingEIP712Transfer) (*contracts.UnchainedStakingSignature, error) {
data := &apitypes.TypedData{
Types: Types,
PrimaryType: "Transfer",
Domain: s.domain,
Message: map[string]interface{}{
"signer": evmSigner.Address,
"from": request.From,
"to": request.To,
"amount": request.Amount,
"nftIds": request.NftIds,
"nonces": request.Nonces,
},
}

return s.signEip712Message(evmSigner, data)
}

func (s *EIP712Signer) SignSetParamsRequest(evmSigner *ethereum.EvmSigner, request *contracts.UnchainedStakingEIP712SetParams) (*contracts.UnchainedStakingSignature, error) {
data := &apitypes.TypedData{
Types: Types,
PrimaryType: "SetParams",
Domain: s.domain,
Message: map[string]interface{}{
"requester": evmSigner.Address,
"token": request.Token,
"nft": request.Nft,
"nftTracker": request.NftTracker,
"threshold": request.Threshold,
"expiration": request.Expiration,
"nonce": request.Nonce,
},
}

return s.signEip712Message(evmSigner, data)
}

func (s *EIP712Signer) SignSetNftPriceRequest(evmSigner *ethereum.EvmSigner, request *contracts.UnchainedStakingEIP712SetNftPrice) (*contracts.UnchainedStakingSignature, error) {
data := &apitypes.TypedData{
Types: Types,
PrimaryType: "SetNftPrice",
Domain: s.domain,
Message: map[string]interface{}{
"requester": evmSigner.Address,
"nftId": request.NftId,
"price": request.Price,
"nonce": request.Nonce,
},
}

return s.signEip712Message(evmSigner, data)
}

func New(chainID *big.Int, verifyingContract string) *EIP712Signer {
return &EIP712Signer{
domain: apitypes.TypedDataDomain{
Name: "Unchained",
Version: "1.0.0",
ChainId: math.NewHexOrDecimal256(chainID.Int64()),
VerifyingContract: verifyingContract,
},
}
}
33 changes: 33 additions & 0 deletions internal/pos/eip712/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package eip712

import (
"github.com/ethereum/go-ethereum/signer/core/apitypes"
)

var Types = apitypes.Types{
"EIP712Transfer": {
{Name: "signer", Type: "address"},
{Name: "from", Type: "address"},
{Name: "to", Type: "address"},
{Name: "amount", Type: "uint256"},
{Name: "nftIds", Type: "uint256[]"},
{Name: "nonces", Type: "uint256[]"},
},

"EIP712SetParams": {
{Name: "requester", Type: "address"},
{Name: "token", Type: "address"},
{Name: "nft", Type: "address"},
{Name: "nftTracker", Type: "address"},
{Name: "threshold", Type: "uint256"},
{Name: "expiration", Type: "uint256"},
{Name: "nonce", Type: "uint256"},
},

"EIP712SetNftPrice": {
{Name: "requester", Type: "address"},
{Name: "nftId", Type: "uint256"},
{Name: "price", Type: "uint256"},
{Name: "nonce", Type: "uint256"},
},
}
21 changes: 20 additions & 1 deletion internal/pos/pos.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"

"github.com/KenshiTech/unchained/ethereum"
"github.com/KenshiTech/unchained/pos/eip712"

"github.com/KenshiTech/unchained/address"
"github.com/KenshiTech/unchained/config"
Expand All @@ -21,6 +22,8 @@ type Repository struct {
votingPowers *xsync.MapOf[[20]byte, *big.Int]
lastUpdated *xsync.MapOf[[20]byte, *big.Int]
base *big.Int
evmSigner *ethereum.EvmSigner
eip712Signer *eip712.EIP712Signer
}

func (s *Repository) GetTotalVotingPower() (*big.Int, error) {
Expand Down Expand Up @@ -82,7 +85,11 @@ func (s *Repository) VotingPowerToFloat(power *big.Int) *big.Float {
func New(
ethRPC *ethereum.Repository,
) *Repository {
s := &Repository{ethRPC: ethRPC}
s := &Repository{
ethRPC: ethRPC,
evmSigner: ethereum.EvmSignerInstance,
}

s.init()

s.base = big.NewInt(config.App.ProofOfStake.Base)
Expand Down Expand Up @@ -135,6 +142,18 @@ func New(
With("Network", s.VotingPowerToFloat(total)).
Info("PoS")

chainID, err := s.posContract.GetChainId(nil)

if err != nil {
log.Logger.
With("Error", err).
Error("Failed to get chain ID")

return s
}

s.eip712Signer = eip712.New(chainID, config.App.ProofOfStake.Address)

return s
}

Expand Down

0 comments on commit 551a83e

Please sign in to comment.