mirror of
https://github.com/grassrootseconomics/cic-custodial.git
synced 2026-05-25 04:36:19 +02:00
init: celo wip
This commit is contained in:
41
internal/tasker/task/dispatch.go
Normal file
41
internal/tasker/task/dispatch.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/celo-org/celo-blockchain/common"
|
||||
"github.com/celo-org/celo-blockchain/core/types"
|
||||
celo "github.com/grassrootseconomics/cic-celo-sdk"
|
||||
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
|
||||
"github.com/hibiken/asynq"
|
||||
)
|
||||
|
||||
type TxPayload struct {
|
||||
Tx *types.Transaction `json:"tx"`
|
||||
}
|
||||
|
||||
func TxDispatch(
|
||||
celoProvider *celo.Provider,
|
||||
) func(context.Context, *asynq.Task) error {
|
||||
return func(ctx context.Context, t *asynq.Task) error {
|
||||
var (
|
||||
p TxPayload
|
||||
txHash common.Hash
|
||||
)
|
||||
|
||||
if err := json.Unmarshal(t.Payload(), &p); err != nil {
|
||||
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
if err := celoProvider.Client.CallCtx(
|
||||
ctx,
|
||||
eth.SendTx(p.Tx).Returns(&txHash),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
273
internal/tasker/task/system.go
Normal file
273
internal/tasker/task/system.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/bsm/redislock"
|
||||
celo "github.com/grassrootseconomics/cic-celo-sdk"
|
||||
"github.com/grassrootseconomics/cic-custodial/internal/nonce"
|
||||
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
|
||||
"github.com/grassrootseconomics/w3-celo-patch"
|
||||
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
|
||||
"github.com/hibiken/asynq"
|
||||
)
|
||||
|
||||
type SystemPayload struct {
|
||||
PublicKey string `json:"publicKey"`
|
||||
}
|
||||
|
||||
func PrepareAccount(
|
||||
nonceProvider nonce.Noncestore,
|
||||
taskerClient *tasker.TaskerClient,
|
||||
) func(context.Context, *asynq.Task) error {
|
||||
return func(ctx context.Context, t *asynq.Task) error {
|
||||
var p SystemPayload
|
||||
if err := json.Unmarshal(t.Payload(), &p); err != nil {
|
||||
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
if err := nonceProvider.SetNewAccountNonce(ctx, p.PublicKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := taskerClient.CreateTask(
|
||||
tasker.GiftGasTask,
|
||||
tasker.DefaultPriority,
|
||||
&tasker.Task{
|
||||
Payload: t.Payload(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = taskerClient.CreateTask(
|
||||
tasker.GiftTokenTask,
|
||||
tasker.DefaultPriority,
|
||||
&tasker.Task{
|
||||
Payload: t.Payload(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GiftGasProcessor(
|
||||
celoProvider *celo.Provider,
|
||||
nonceProvider nonce.Noncestore,
|
||||
lockProvider *redislock.Client,
|
||||
system *tasker.SystemContainer,
|
||||
taskerClient *tasker.TaskerClient,
|
||||
) func(context.Context, *asynq.Task) error {
|
||||
return func(ctx context.Context, t *asynq.Task) error {
|
||||
var p SystemPayload
|
||||
if err := json.Unmarshal(t.Payload(), &p); err != nil {
|
||||
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
lock, err := lockProvider.Obtain(ctx, system.LockPrefix+system.PublicKey, system.LockTimeout, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer lock.Release(ctx)
|
||||
|
||||
nonce, err := nonceProvider.Acquire(ctx, system.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
builtTx, err := celoProvider.SignGasTransferTx(
|
||||
system.PrivateKey,
|
||||
celo.GasTransferTxOpts{
|
||||
To: w3.A(p.PublicKey),
|
||||
Nonce: nonce,
|
||||
Value: system.GiftableGasValue,
|
||||
GasPrice: celo.FixedMinGas,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if err := nonceProvider.Return(ctx, p.PublicKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("nonce.Return failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
disptachJobPayload, err := json.Marshal(TxPayload{
|
||||
Tx: builtTx,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("json.Marshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
_, err = taskerClient.CreateTask(
|
||||
tasker.TxDispatchTask,
|
||||
tasker.HighPriority,
|
||||
&tasker.Task{
|
||||
Payload: disptachJobPayload,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GiftTokenProcessor(
|
||||
celoProvider *celo.Provider,
|
||||
nonceProvider nonce.Noncestore,
|
||||
lockProvider *redislock.Client,
|
||||
system *tasker.SystemContainer,
|
||||
taskerClient *tasker.TaskerClient,
|
||||
) func(context.Context, *asynq.Task) error {
|
||||
return func(ctx context.Context, t *asynq.Task) error {
|
||||
var p SystemPayload
|
||||
if err := json.Unmarshal(t.Payload(), &p); err != nil {
|
||||
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
publicKey := w3.A(p.PublicKey)
|
||||
|
||||
lock, err := lockProvider.Obtain(ctx, system.LockPrefix+system.PublicKey, system.LockTimeout, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer lock.Release(ctx)
|
||||
|
||||
nonce, err := nonceProvider.Acquire(ctx, system.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
abi, err := w3.NewFunc("mint(address,uint256)", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
input, err := abi.EncodeArgs(publicKey, system.GiftableTokenValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ABI encode failed %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
builtTx, err := celoProvider.SignContractExecutionTx(
|
||||
system.PrivateKey,
|
||||
celo.ContractExecutionTxOpts{
|
||||
ContractAddress: system.GiftableToken,
|
||||
InputData: input,
|
||||
GasPrice: celo.FixedMinGas,
|
||||
GasLimit: system.TokenTransferGasLimit,
|
||||
Nonce: nonce,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if err := nonceProvider.Return(ctx, p.PublicKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("nonce.Return failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
disptachJobPayload, err := json.Marshal(TxPayload{
|
||||
Tx: builtTx,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("json.Marshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
_, err = taskerClient.CreateTask(
|
||||
tasker.TxDispatchTask,
|
||||
tasker.HighPriority,
|
||||
&tasker.Task{
|
||||
Payload: disptachJobPayload,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func RefillGasProcessor(
|
||||
celoProvider *celo.Provider,
|
||||
nonceProvider nonce.Noncestore,
|
||||
lockProvider *redislock.Client,
|
||||
system *tasker.SystemContainer,
|
||||
taskerClient *tasker.TaskerClient,
|
||||
) func(context.Context, *asynq.Task) error {
|
||||
return func(ctx context.Context, t *asynq.Task) error {
|
||||
var (
|
||||
p SystemPayload
|
||||
balance big.Int
|
||||
)
|
||||
if err := json.Unmarshal(t.Payload(), &p); err != nil {
|
||||
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
if err := celoProvider.Client.CallCtx(
|
||||
ctx,
|
||||
eth.Balance(w3.A(p.PublicKey), nil).Returns(&balance),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if belowThreshold := balance.Cmp(system.GasRefillThreshold); belowThreshold > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
lock, err := lockProvider.Obtain(ctx, system.LockPrefix+system.PublicKey, system.LockTimeout, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer lock.Release(ctx)
|
||||
|
||||
nonce, err := nonceProvider.Acquire(ctx, system.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
builtTx, err := celoProvider.SignGasTransferTx(
|
||||
system.PrivateKey,
|
||||
celo.GasTransferTxOpts{
|
||||
To: w3.A(p.PublicKey),
|
||||
Nonce: nonce,
|
||||
Value: system.GasRefillValue,
|
||||
GasPrice: celo.FixedMinGas,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if err := nonceProvider.Return(ctx, p.PublicKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("nonce.Return failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
disptachJobPayload, err := json.Marshal(TxPayload{
|
||||
Tx: builtTx,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("json.Marshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
_, err = taskerClient.CreateTask(
|
||||
tasker.TxDispatchTask,
|
||||
tasker.HighPriority,
|
||||
&tasker.Task{
|
||||
Payload: disptachJobPayload,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
129
internal/tasker/task/transfer.go
Normal file
129
internal/tasker/task/transfer.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/bsm/redislock"
|
||||
celo "github.com/grassrootseconomics/cic-celo-sdk"
|
||||
"github.com/grassrootseconomics/cic-custodial/internal/keystore"
|
||||
"github.com/grassrootseconomics/cic-custodial/internal/nonce"
|
||||
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
|
||||
"github.com/grassrootseconomics/w3-celo-patch"
|
||||
"github.com/hibiken/asynq"
|
||||
)
|
||||
|
||||
type TransferPayload struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
VoucherAddress string `json:"voucherAddress"`
|
||||
Amount string `json:"amount"`
|
||||
}
|
||||
|
||||
func TransferToken(
|
||||
celoProvider *celo.Provider,
|
||||
nonceProvider nonce.Noncestore,
|
||||
keystoreProvider keystore.Keystore,
|
||||
lockProvider *redislock.Client,
|
||||
system *tasker.SystemContainer,
|
||||
taskerClient *tasker.TaskerClient,
|
||||
) func(context.Context, *asynq.Task) error {
|
||||
return func(ctx context.Context, t *asynq.Task) error {
|
||||
var p TransferPayload
|
||||
|
||||
if err := json.Unmarshal(t.Payload(), &p); err != nil {
|
||||
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
lock, err := lockProvider.Obtain(ctx, system.LockPrefix+p.From, system.LockTimeout, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer lock.Release(ctx)
|
||||
|
||||
nonce, err := nonceProvider.Acquire(ctx, p.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := keystoreProvider.LoadPrivateKey(ctx, p.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
abi, err := w3.NewFunc("transfer(address,uint256)", "bool")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
input, err := abi.EncodeArgs(p.To, parseTransferValue(p.Amount, system.TokenDecimals))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ABI encode failed %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
builtTx, err := celoProvider.SignContractExecutionTx(
|
||||
key,
|
||||
celo.ContractExecutionTxOpts{
|
||||
ContractAddress: system.GiftableToken,
|
||||
InputData: input,
|
||||
GasPrice: celo.FixedMinGas,
|
||||
GasLimit: system.TokenTransferGasLimit,
|
||||
Nonce: nonce,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if err := nonceProvider.Return(ctx, p.From); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("nonce.Return failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
disptachJobPayload, err := json.Marshal(TxPayload{
|
||||
Tx: builtTx,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("json.Marshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
_, err = taskerClient.CreateTask(
|
||||
tasker.TxDispatchTask,
|
||||
tasker.HighPriority,
|
||||
&tasker.Task{
|
||||
Payload: disptachJobPayload,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasRefillPayload, err := json.Marshal(SystemPayload{
|
||||
PublicKey: p.From,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("json.Marshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
_, err = taskerClient.CreateTask(
|
||||
tasker.RefillGasTask,
|
||||
tasker.DefaultPriority,
|
||||
&tasker.Task{
|
||||
Payload: gasRefillPayload,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func parseTransferValue(value string, tokenDecimals int) *big.Int {
|
||||
floatValue, _ := strconv.ParseFloat(value, 64)
|
||||
|
||||
return big.NewInt(int64(floatValue * math.Pow10(tokenDecimals)))
|
||||
}
|
||||
68
internal/tasker/task/transfer_test.go
Normal file
68
internal/tasker/task/transfer_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_parseTransferValue(t *testing.T) {
|
||||
type args struct {
|
||||
value string
|
||||
tokenDecimals int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *big.Int
|
||||
}{
|
||||
{
|
||||
name: "zero value string",
|
||||
args: args{
|
||||
value: "0",
|
||||
tokenDecimals: 6,
|
||||
},
|
||||
want: big.NewInt(0),
|
||||
},
|
||||
{
|
||||
name: "fixed value string",
|
||||
args: args{
|
||||
value: "2",
|
||||
tokenDecimals: 6,
|
||||
},
|
||||
want: big.NewInt(2000000),
|
||||
},
|
||||
{
|
||||
name: "float (2 d.p) value string",
|
||||
args: args{
|
||||
value: "2.19",
|
||||
tokenDecimals: 6,
|
||||
},
|
||||
want: big.NewInt(2190000),
|
||||
},
|
||||
{
|
||||
name: "float (6 d.p) value string",
|
||||
args: args{
|
||||
value: "2.123456",
|
||||
tokenDecimals: 6,
|
||||
},
|
||||
want: big.NewInt(2123456),
|
||||
},
|
||||
{
|
||||
name: "float (10 d.p) value string",
|
||||
args: args{
|
||||
value: "2.1234567891",
|
||||
tokenDecimals: 6,
|
||||
},
|
||||
want: big.NewInt(2123456),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseTransferValue(tt.args.value, tt.args.tokenDecimals)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseValue() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user