-
Notifications
You must be signed in to change notification settings - Fork 1
Rewards Part 1 #34
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
Rewards Part 1 #34
Changes from all commits
d28a49e
dd977b1
daa00cd
0dcff70
985a4b2
3cbd259
3e5ede8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| 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) | ||
| AND (@specifier::text = '' OR user_challenges.specifier = @specifier) | ||
| ; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package reward_manager | ||
|
|
||
| type ChangeManagerAccount struct{} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package reward_manager | ||
|
|
||
| type CreateSender struct{} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package reward_manager | ||
|
|
||
| type CreateSenderPublic struct{} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package reward_manager | ||
|
|
||
| type DeleteSender struct{} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package reward_manager | ||
|
|
||
| type DeleteSenderPublic struct{} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package reward_manager | ||
|
|
||
| type EvaluateAttestation struct{} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package reward_manager | ||
|
|
||
| type Init struct{} |
| 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) | ||
| } |
| 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") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be per environment? (stage / prod / etc )
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } | ||
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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