-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
785 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package config | ||
|
||
import "storj.io/crypto-batch-payment/pkg/payer" | ||
|
||
type Auditor interface { | ||
payer.Auditor | ||
Close() | ||
} | ||
|
||
type auditorWrapper struct { | ||
payer.Auditor | ||
closeFunc func() | ||
} | ||
|
||
func (w auditorWrapper) Close() { | ||
if w.closeFunc != nil { | ||
w.closeFunc() | ||
} | ||
} | ||
|
||
type Auditors map[payer.Type]Auditor | ||
|
||
func (as *Auditors) Add(t payer.Type, a Auditor) { | ||
if *as == nil { | ||
*as = make(map[payer.Type]Auditor) | ||
} | ||
(*as)[t] = a | ||
} | ||
|
||
func (as Auditors) Close() { | ||
for _, a := range as { | ||
a.Close() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package config | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/zeebo/errs" | ||
|
||
"storj.io/crypto-batch-payment/pkg/coinmarketcap" | ||
) | ||
|
||
type CoinMarketCap struct { | ||
APIURL string `toml:"api_url"` | ||
APIKeyPath Path `toml:"api_key_path"` | ||
CacheExpiry Duration `toml:"cache_expiry"` | ||
} | ||
|
||
func (c CoinMarketCap) NewQuoter() (coinmarketcap.Quoter, error) { | ||
apiKey, err := loadFirstLine(string(c.APIKeyPath)) | ||
if err != nil { | ||
return nil, errs.New("failed to load CoinMarketCap key: %v\n", err) | ||
} | ||
|
||
quoter, err := coinmarketcap.NewCachingClient(c.APIURL, apiKey, time.Duration(c.CacheExpiry)) | ||
if err != nil { | ||
return nil, errs.New("failed instantiate coinmarketcap client: %v\n", err) | ||
} | ||
|
||
return quoter, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package config | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/pelletier/go-toml/v2" | ||
|
||
"storj.io/crypto-batch-payment/pkg/coinmarketcap" | ||
"storj.io/crypto-batch-payment/pkg/payer" | ||
"storj.io/crypto-batch-payment/pkg/pipeline" | ||
) | ||
|
||
type Config struct { | ||
Pipeline Pipeline `toml:"pipeline"` | ||
CoinMarketCap CoinMarketCap `toml:"coinmarketcap"` | ||
Eth *Eth `toml:"eth"` | ||
ZkSync *ZkSync `toml:"zksync"` | ||
ZkSyncEra *ZkSyncEra `toml:"zksync-era"` | ||
} | ||
|
||
func (c *Config) NewPayers(ctx context.Context) (_ Payers, err error) { | ||
var payers Payers | ||
defer func() { | ||
if err != nil { | ||
payers.Close() | ||
} | ||
}() | ||
|
||
if c.Eth != nil { | ||
p, err := c.Eth.NewPayer(ctx) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to init eth payer: %w", err) | ||
} | ||
payers.Add(payer.Eth, p) | ||
} | ||
|
||
if c.ZkSync != nil { | ||
p, err := c.ZkSync.NewPayer(ctx) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to init zksync payer: %w", err) | ||
} | ||
payers.Add(payer.ZkSync, p) | ||
} | ||
|
||
if c.ZkSyncEra != nil { | ||
p, err := c.ZkSyncEra.NewPayer(ctx) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to init zksync-era payer: %w", err) | ||
} | ||
payers.Add(payer.ZkSyncEra, p) | ||
} | ||
|
||
return payers, nil | ||
} | ||
|
||
func (c *Config) NewAuditors(ctx context.Context) (_ Auditors, err error) { | ||
var auditors Auditors | ||
defer func() { | ||
if err != nil { | ||
auditors.Close() | ||
} | ||
}() | ||
|
||
if c.Eth != nil { | ||
p, err := c.Eth.NewAuditor(ctx) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to init eth auditor: %w", err) | ||
} | ||
auditors.Add(payer.Eth, p) | ||
} | ||
|
||
if c.ZkSync != nil { | ||
p, err := c.ZkSync.NewAuditor(ctx) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to init zksync auditor: %w", err) | ||
} | ||
auditors.Add(payer.ZkSync, p) | ||
} | ||
|
||
if c.ZkSyncEra != nil { | ||
p, err := c.ZkSyncEra.NewAuditor(ctx) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to init zksync-era auditor: %w", err) | ||
} | ||
auditors.Add(payer.ZkSyncEra, p) | ||
} | ||
|
||
return auditors, nil | ||
} | ||
|
||
type Pipeline struct { | ||
DepthLimit int `toml:"depth_limit"` | ||
TxDelay Duration `toml:"tx_delay"` | ||
} | ||
|
||
func Load(path string) (Config, error) { | ||
data, err := os.ReadFile(path) | ||
if err != nil { | ||
return Config{}, fmt.Errorf("failed to read config: %w", err) | ||
} | ||
return Parse(data) | ||
} | ||
|
||
func Parse(data []byte) (Config, error) { | ||
const ( | ||
defaultPipelineDepthLimit = pipeline.DefaultLimit | ||
defaultPipelineTxDelay = Duration(pipeline.DefaultTxDelay) | ||
defaultCoinMarketCapAPIURL = coinmarketcap.ProductionAPIURL | ||
defaultCoinMarketCapKeyPath = "~/.coinmarketcap" | ||
defaultCoinMarketCapCacheExpiry = time.Second * 5 | ||
) | ||
|
||
config := Config{ | ||
Pipeline: Pipeline{ | ||
DepthLimit: defaultPipelineDepthLimit, | ||
TxDelay: defaultPipelineTxDelay, | ||
}, | ||
CoinMarketCap: CoinMarketCap{ | ||
APIURL: defaultCoinMarketCapAPIURL, | ||
APIKeyPath: ToPath(defaultCoinMarketCapKeyPath), | ||
CacheExpiry: Duration(defaultCoinMarketCapCacheExpiry), | ||
}, | ||
} | ||
|
||
d := toml.NewDecoder(bytes.NewReader(data)) | ||
d.DisallowUnknownFields() | ||
if err := d.Decode(&config); err != nil { | ||
return Config{}, fmt.Errorf("failed to unmarshal config: %w", err) | ||
} | ||
|
||
return config, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package config_test | ||
|
||
import ( | ||
"math/big" | ||
"os/user" | ||
"path/filepath" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"storj.io/crypto-batch-payment/pkg/config" | ||
) | ||
|
||
func TestLoad_Defaults(t *testing.T) { | ||
currentUser, err := user.Current() | ||
require.NoError(t, err) | ||
|
||
homePath := func(suffix string) config.Path { | ||
return config.Path(filepath.Join(currentUser.HomeDir, suffix)) | ||
} | ||
|
||
cfg, err := config.Load("./testdata/defaults.toml") | ||
require.NoError(t, err) | ||
|
||
assert.Equal(t, config.Config{ | ||
Pipeline: config.Pipeline{ | ||
DepthLimit: 16, | ||
TxDelay: 0, | ||
}, | ||
CoinMarketCap: config.CoinMarketCap{ | ||
APIURL: "https://pro-api.coinmarketcap.com", | ||
APIKeyPath: homePath(".coinmarketcap"), | ||
CacheExpiry: 5000000000, | ||
}, | ||
Eth: &config.Eth{ | ||
NodeAddress: "https://someaddress.test", | ||
SpenderKeyPath: homePath("some.key"), | ||
ERC20ContractAddress: common.HexToAddress("0x1111111111111111111111111111111111111111"), | ||
ChainID: 0, | ||
Owner: nil, | ||
MaxGas: nil, | ||
GasTipCap: nil, | ||
}, | ||
ZkSync: &config.ZkSync{ | ||
NodeAddress: "https://api.zksync.io", | ||
SpenderKeyPath: homePath("some.key"), | ||
ChainID: 0, | ||
MaxFee: nil, | ||
}, | ||
ZkSyncEra: &config.ZkSyncEra{ | ||
NodeAddress: "https://mainnet.era.zksync.io", | ||
SpenderKeyPath: homePath("some.key"), | ||
ERC20ContractAddress: common.HexToAddress("0x2222222222222222222222222222222222222222"), | ||
ChainID: 0, | ||
MaxFee: nil, | ||
PaymasterAddress: nil, | ||
PaymasterPayload: nil, | ||
}, | ||
}, cfg) | ||
} | ||
|
||
func TestLoad_Overrides(t *testing.T) { | ||
cfg, err := config.Load("./testdata/override.toml") | ||
require.NoError(t, err) | ||
|
||
assert.Equal(t, config.Config{ | ||
Pipeline: config.Pipeline{ | ||
DepthLimit: 24, | ||
TxDelay: config.Duration(time.Minute), | ||
}, | ||
CoinMarketCap: config.CoinMarketCap{ | ||
APIURL: "https://override.test", | ||
APIKeyPath: "override", | ||
CacheExpiry: 5000000000, | ||
}, | ||
Eth: &config.Eth{ | ||
NodeAddress: "https://override.test", | ||
SpenderKeyPath: "override", | ||
ERC20ContractAddress: common.HexToAddress("0xe66652d41EE7e81d3fcAe1dF7F9B9f9411ac835e"), | ||
ChainID: 12345, | ||
Owner: ptrOf(common.HexToAddress("0xe66652d41EE7e81d3fcAe1dF7F9B9f9411ac835e")), | ||
MaxGas: big.NewInt(80_000_000_000), | ||
GasTipCap: big.NewInt(2_000_000_000), | ||
}, | ||
ZkSync: &config.ZkSync{ | ||
NodeAddress: "https://override.test", | ||
SpenderKeyPath: "override", | ||
ChainID: 12345, | ||
MaxFee: big.NewInt(1234), | ||
}, | ||
ZkSyncEra: &config.ZkSyncEra{ | ||
NodeAddress: "https://override.test", | ||
SpenderKeyPath: "override", | ||
ERC20ContractAddress: common.HexToAddress("0xe66652d41EE7e81d3fcAe1dF7F9B9f9411ac835e"), | ||
ChainID: 12345, | ||
MaxFee: big.NewInt(5678), | ||
PaymasterAddress: ptrOf(common.HexToAddress("0xe66652d41EE7e81d3fcAe1dF7F9B9f9411ac835e")), | ||
PaymasterPayload: []byte("\x01\x23"), | ||
}, | ||
}, cfg) | ||
} | ||
|
||
func ptrOf[T any](t T) *T { | ||
return &t | ||
} |
Oops, something went wrong.