Skip to content
Merged
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
69 changes: 69 additions & 0 deletions api/dbv1/get_undisbursed_challenges.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions api/dbv1/queries/get_undisbursed_challenges.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- name: GetUndisbursedChallenges :many
SELECT
users.handle,
users.wallet,
user_challenges.challenge_id,
user_challenges.specifier
FROM user_challenges
JOIN users ON users.user_id = user_challenges.user_id
LEFT JOIN challenge_disbursements
ON challenge_disbursements.challenge_id = user_challenges.challenge_id
AND challenge_disbursements.specifier = user_challenges.specifier
WHERE
challenge_disbursements.challenge_id IS NULL
AND user_challenges.is_complete
AND user_challenges.user_id = @user_id
AND (@challenge_id::text = '' OR user_challenges.challenge_id = @challenge_id)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is ::text necessary here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this made sure it typed as a string when autogenerating the model

AND (@specifier::text = '' OR user_challenges.specifier = @specifier)
;
3 changes: 3 additions & 0 deletions api/solana/programs/reward-manager/ChangeManagerAccount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reward_manager

type ChangeManagerAccount struct{}
3 changes: 3 additions & 0 deletions api/solana/programs/reward-manager/CreateSender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reward_manager

type CreateSender struct{}
3 changes: 3 additions & 0 deletions api/solana/programs/reward-manager/CreateSenderPublic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reward_manager

type CreateSenderPublic struct{}
3 changes: 3 additions & 0 deletions api/solana/programs/reward-manager/DeleteSender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reward_manager

type DeleteSender struct{}
3 changes: 3 additions & 0 deletions api/solana/programs/reward-manager/DeleteSenderPublic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reward_manager

type DeleteSenderPublic struct{}
3 changes: 3 additions & 0 deletions api/solana/programs/reward-manager/EvaluateAttestations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reward_manager

type EvaluateAttestation struct{}
3 changes: 3 additions & 0 deletions api/solana/programs/reward-manager/Init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reward_manager

type Init struct{}
153 changes: 153 additions & 0 deletions api/solana/programs/reward-manager/SubmitAttestation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package reward_manager

import (
"encoding/hex"

bin "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
)

const (
SenderSeedPrefix = "S_"
EthAddressByteLength = 20
AttestationsSeedPrefix = "V_"
)

type SubmitAttestation struct {
DisbursementID string
SenderEthAddress string `bin:"-" borsh_skip:"true"`
RewardManagerState solana.PublicKey `bin:"-" borsh_skip:"true"`
Payer solana.PublicKey `bin:"-" borsh_skip:"true"`

solana.AccountMetaSlice `bin:"-" borsh_skip:"true"`
}

func NewSubmitAttestationInstructionBuilder() *SubmitAttestation {
nd := &SubmitAttestation{}
return nd
}

func (inst *SubmitAttestation) SetDisbursementID(challengeId string, specifier string) *SubmitAttestation {
inst.DisbursementID = challengeId + ":" + specifier
return inst
}

func (inst *SubmitAttestation) SetSenderEthAddress(senderEthAddress string) *SubmitAttestation {
inst.SenderEthAddress = senderEthAddress
return inst
}

func (inst *SubmitAttestation) SetRewardManagerState(state solana.PublicKey) *SubmitAttestation {
inst.RewardManagerState = state
return inst
}

func (inst *SubmitAttestation) SetPayer(payer solana.PublicKey) *SubmitAttestation {
inst.Payer = payer
return inst
}

func deriveAuthority(programId solana.PublicKey, state solana.PublicKey) (solana.PublicKey, uint8, error) {
seeds := make([][]byte, 1)
seeds[0] = state.Bytes()[0:32]
return solana.FindProgramAddress(seeds, programId)
}

func deriveSender(programId solana.PublicKey, authority solana.PublicKey, ethAddress string) (solana.PublicKey, uint8, error) {

senderSeedPrefix := []byte(SenderSeedPrefix)
// Remove 0x and decode hex
decodedEthAddress, err := hex.DecodeString(ethAddress[2:])
if err != nil {
return solana.PublicKey{}, 0, err
}
// Pad the eth address if necessary w/ leading 0
senderSeed := make([]byte, len(senderSeedPrefix)+EthAddressByteLength)
copy(senderSeed, senderSeedPrefix)
copy(senderSeed[len(senderSeed)-len(decodedEthAddress):], decodedEthAddress)
return solana.FindProgramAddress([][]byte{authority.Bytes()[0:32], senderSeed}, programId)
}

func deriveAttestations(programId solana.PublicKey, authority solana.PublicKey, disbursementId string) (solana.PublicKey, uint8, error) {
attestationsSeed := make([]byte, len(AttestationsSeedPrefix)+len(disbursementId))
copy(attestationsSeed, []byte(AttestationsSeedPrefix))
copy(attestationsSeed[len([]byte(AttestationsSeedPrefix)):], disbursementId)
return solana.FindProgramAddress([][]byte{authority.Bytes()[0:32], attestationsSeed}, programId)
}

func (inst SubmitAttestation) Build() *Instruction {
authority, _, _ := deriveAuthority(ProgramID, inst.RewardManagerState)
sender, _, _ := deriveSender(ProgramID, authority, inst.SenderEthAddress)
attestations, _, _ := deriveAttestations(ProgramID, authority, inst.DisbursementID)

inst.AccountMetaSlice = []*solana.AccountMeta{
{
PublicKey: attestations,
IsSigner: false,
IsWritable: true,
},
{
PublicKey: inst.RewardManagerState,
IsSigner: false,
IsWritable: false,
},
{
PublicKey: authority,
IsSigner: false,
IsWritable: false,
},
{
PublicKey: inst.Payer,
IsSigner: true,
IsWritable: false,
},
{
PublicKey: sender,
IsSigner: false,
IsWritable: false,
},
{
PublicKey: solana.SysVarRentPubkey,
IsSigner: false,
IsWritable: false,
},
{
PublicKey: solana.SysVarInstructionsPubkey,
IsSigner: false,
IsWritable: false,
},
{
PublicKey: solana.SystemProgramID,
IsSigner: false,
IsWritable: false,
},
}

return &Instruction{BaseVariant: bin.BaseVariant{
Impl: inst,
TypeID: bin.TypeIDFromUint8(Instruction_SubmitAttestation),
}}
}

func (inst SubmitAttestation) MarshalWithEncoder(encoder *bin.Encoder) error {
return encoder.WriteString(inst.DisbursementID)
}

func (inst *SubmitAttestation) UnmarshalWithDecoder(decoder *bin.Decoder) error {
return decoder.Decode(&inst.DisbursementID)
}

func NewSubmitAttestationInstruction(
challengeId string,
specifier string,
senderEthAddress string,
rewardManagerState solana.PublicKey,
payer solana.PublicKey,

) *SubmitAttestation {
return NewSubmitAttestationInstructionBuilder().
SetRewardManagerState(rewardManagerState).
SetDisbursementID(challengeId, specifier).
SetSenderEthAddress(senderEthAddress).
SetPayer(payer)
}
120 changes: 120 additions & 0 deletions api/solana/programs/reward-manager/instruction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package reward_manager

import (
"bytes"
"fmt"

bin "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
ag_text "github.com/gagliardetto/solana-go/text"
)

var ProgramID = solana.MustPublicKeyFromBase58("CDpzvz7DfgbF95jSSCHLX3ERkugyfgn9Fw8ypNZ1hfXp")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be per environment? (stage / prod / etc )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah - I'm imitating the other programs in solana-go - I'll have the program id change on init using the method


func SetProgramID(pubkey solana.PublicKey) {
ProgramID = pubkey
solana.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
}

func init() {
solana.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
}

const (
Instruction_Init uint8 = iota
Instruction_ChangeManagerAccount
Instruction_CreateSender
Instruction_DeleteSender
Instruction_CreateSenderPublic
Instruction_DeleteSenderPublic
Instruction_SubmitAttestation
Instruction_EvaluateAttestations
)

type Instruction struct {
bin.BaseVariant
}

func (inst *Instruction) Accounts() (out []*solana.AccountMeta) {
return inst.Impl.(solana.AccountsGettable).GetAccounts()
}

func (inst *Instruction) Data() ([]byte, error) {
buf := new(bytes.Buffer)
if err := bin.NewBorshEncoder(buf).Encode(inst); err != nil {
return nil, fmt.Errorf("unable to encode instruction: %w", err)
}
return buf.Bytes(), nil
}

func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
return encoder.Encode(inst.Impl, option)
}

var InstructionImplDef = bin.NewVariantDefinition(
bin.Uint8TypeIDEncoding,
[]bin.VariantType{
{
Name: "Init", Type: (*SubmitAttestation)(nil),
},
{
Name: "ChangeManagerAccount", Type: (*SubmitAttestation)(nil),
},
{
Name: "CreateSender", Type: (*SubmitAttestation)(nil),
},
{
Name: "DeleteSender", Type: (*SubmitAttestation)(nil),
},
{
Name: "CreateSenderPublic", Type: (*SubmitAttestation)(nil),
},
{
Name: "DeleteSenderPublic", Type: (*SubmitAttestation)(nil),
},
{
Name: "SubmitAttestation", Type: (*SubmitAttestation)(nil),
},
{
Name: "EvaluateAttestation", Type: (*SubmitAttestation)(nil),
},
},
)

func (inst *Instruction) ProgramID() solana.PublicKey {
return ProgramID
}

func (inst *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) error {
return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
}

func (inst Instruction) MarshalWithEncoder(encoder *bin.Encoder) error {
err := encoder.WriteUint8(inst.TypeID.Uint8())
if err != nil {
return fmt.Errorf("unable to write variant type: %w", err)
}
return encoder.Encode(inst.Impl)
}

func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (interface{}, error) {
inst, err := DecodeInstruction(accounts, data)
if err != nil {
return nil, err
}
return inst, nil
}

func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) {
inst := new(Instruction)
if err := bin.NewBorshDecoder(data).Decode(inst); err != nil {
return nil, fmt.Errorf("unable to decode instruction: %w", err)
}
if v, ok := inst.Impl.(solana.AccountsSettable); ok {
err := v.SetAccounts(accounts)
if err != nil {
return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
}
}
return inst, nil
}
Loading