-
Notifications
You must be signed in to change notification settings - Fork 13
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
1 parent
5dcc6eb
commit 5ec3939
Showing
18 changed files
with
704 additions
and
14 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package etherman2 | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/0xPolygon/cdk/etherman2/internal" | ||
"github.com/ethereum/go-ethereum/ethclient" | ||
) | ||
|
||
type etherman2Impl struct { | ||
*internal.Etherman2AuthStore | ||
*internal.Etherman2ChainReader | ||
} | ||
|
||
/* | ||
Example of usage: | ||
etherman2builder := etherman2.NewEtherman2Builder(). | ||
ChainReader("http://localhost:8545"). | ||
AuthStore(nil) | ||
etherman2, err:= etherman2builder.Build(context.Background()) | ||
*/ | ||
|
||
type Etherman2Builder struct { | ||
L1EthClient *ethclient.Client | ||
chainReader struct { | ||
enabled bool | ||
l1url string | ||
} | ||
authStore *struct { | ||
enabled bool | ||
forcedChainID *uint64 | ||
} | ||
result etherman2Impl | ||
} | ||
|
||
func NewEtherman2Builder() *Etherman2Builder { | ||
return &Etherman2Builder{} | ||
} | ||
|
||
func (e *Etherman2Builder) ChainReader(l1url string) *Etherman2Builder { | ||
e.chainReader.enabled = true | ||
e.chainReader.l1url = l1url | ||
return e | ||
} | ||
|
||
func (e *Etherman2Builder) AuthStore(forcedChainID *uint64) *Etherman2Builder { | ||
e.authStore.enabled = true | ||
e.authStore.forcedChainID = forcedChainID | ||
return e | ||
} | ||
|
||
func (e *Etherman2Builder) Build(ctx context.Context) (*etherman2Impl, error) { | ||
if e.chainReader.enabled { | ||
err := e.newChainReader() | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
if e.authStore.enabled { | ||
err := e.newAuthStore(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
return &e.result, nil | ||
} | ||
|
||
func (e *Etherman2Builder) newChainReader() error { | ||
if e.chainReader.enabled { | ||
client, err := ethclient.Dial(e.chainReader.l1url) | ||
if err != nil { | ||
return err | ||
} | ||
e.L1EthClient = client | ||
} | ||
if e.chainReader.enabled { | ||
var err error | ||
e.result.Etherman2ChainReader, err = internal.NewEtherman2ChainReader(e.L1EthClient) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
return nil | ||
} | ||
|
||
func (e *Etherman2Builder) newAuthStore(ctx context.Context) error { | ||
if e.authStore.enabled { | ||
chainID, err := e.getChainID(ctx) | ||
e.result.Etherman2AuthStore, err = internal.NewEtherman2L1Auth(chainID) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (e *Etherman2Builder) getChainID(ctx context.Context) (uint64, error) { | ||
var chainID uint64 | ||
if e.authStore.forcedChainID != nil { | ||
chainID = *e.authStore.forcedChainID | ||
return chainID, nil | ||
|
||
} | ||
if e.result.Etherman2ChainReader == nil { | ||
return 0, fmt.Errorf("chain reader not initialized, so or force ChainID or build also a chain reader (ChainReader() )") | ||
} | ||
chainID, err := e.result.Etherman2ChainReader.ChainID(ctx) | ||
if err != nil { | ||
return 0, err | ||
} | ||
return chainID, 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,52 @@ | ||
package etherman2 | ||
|
||
import ( | ||
"errors" | ||
"strings" | ||
) | ||
|
||
var ( | ||
// ErrGasRequiredExceedsAllowance gas required exceeds the allowance | ||
ErrGasRequiredExceedsAllowance = errors.New("gas required exceeds allowance") | ||
// ErrContentLengthTooLarge content length is too large | ||
ErrContentLengthTooLarge = errors.New("content length too large") | ||
//ErrTimestampMustBeInsideRange Timestamp must be inside range | ||
ErrTimestampMustBeInsideRange = errors.New("timestamp must be inside range") | ||
//ErrInsufficientAllowance insufficient allowance | ||
ErrInsufficientAllowance = errors.New("insufficient allowance") | ||
// ErrBothGasPriceAndMaxFeeGasAreSpecified both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified | ||
ErrBothGasPriceAndMaxFeeGasAreSpecified = errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") | ||
// ErrMaxFeeGasAreSpecifiedButLondonNotActive maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet | ||
ErrMaxFeeGasAreSpecifiedButLondonNotActive = errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet") | ||
// ErrNoSigner no signer to authorize the transaction with | ||
ErrNoSigner = errors.New("no signer to authorize the transaction with") | ||
// ErrMissingTrieNode means that a node is missing on the trie | ||
ErrMissingTrieNode = errors.New("missing trie node") | ||
// ErrNotFound is used when the object is not found | ||
ErrNotFound = errors.New("not found") | ||
// ErrPrivateKeyNotFound used when the provided sender does not have a private key registered to be used | ||
ErrPrivateKeyNotFound = errors.New("can't find sender private key to sign tx") | ||
|
||
errorsCache = map[string]error{ | ||
ErrGasRequiredExceedsAllowance.Error(): ErrGasRequiredExceedsAllowance, | ||
ErrContentLengthTooLarge.Error(): ErrContentLengthTooLarge, | ||
ErrTimestampMustBeInsideRange.Error(): ErrTimestampMustBeInsideRange, | ||
ErrInsufficientAllowance.Error(): ErrInsufficientAllowance, | ||
ErrBothGasPriceAndMaxFeeGasAreSpecified.Error(): ErrBothGasPriceAndMaxFeeGasAreSpecified, | ||
ErrMaxFeeGasAreSpecifiedButLondonNotActive.Error(): ErrMaxFeeGasAreSpecifiedButLondonNotActive, | ||
ErrNoSigner.Error(): ErrNoSigner, | ||
ErrMissingTrieNode.Error(): ErrMissingTrieNode, | ||
} | ||
) | ||
|
||
func TryParseError(err error) (error, bool) { | ||
parsedError, exists := errorsCache[err.Error()] | ||
if !exists { | ||
for errStr, actualErr := range errorsCache { | ||
if strings.Contains(err.Error(), errStr) { | ||
return actualErr, true | ||
} | ||
} | ||
} | ||
return parsedError, exists | ||
} |
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 etherman2 | ||
|
||
import ( | ||
"context" | ||
"crypto/ecdsa" | ||
|
||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
) | ||
|
||
type Etherman2ChainReader interface { | ||
LastL1BlockNumber(ctx context.Context) (uint64, error) | ||
ChainID(ctx context.Context) (uint64, error) | ||
} | ||
|
||
type Etherman2AuthStorerRO interface { | ||
GetAuthByAddress(addr common.Address) (bind.TransactOpts, bool) | ||
} | ||
|
||
// Etherman2AuthStorerRW is an interface that extends Etherman2AuthStorerRO providing write capabilities | ||
type Etherman2AuthStorerRW interface { | ||
Etherman2AuthStorerRO | ||
LoadAuthFromKeyStore(path, password string) (*bind.TransactOpts, *ecdsa.PrivateKey, error) | ||
AddOrReplaceAuth(auth bind.TransactOpts) error | ||
} | ||
|
||
type Etherman2ContractLoader[T any] interface { | ||
LoadContract(address common.Address, backend bind.ContractBackend, funcConstructor func(common.Address, bind.ContractBackend) (T, error)) (T, error) | ||
} | ||
|
||
type Etherman2 interface { | ||
Etherman2ChainReader | ||
Etherman2AuthStorerRW | ||
} |
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,85 @@ | ||
package internal | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"math/big" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/0xPolygon/cdk/log" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/accounts/keystore" | ||
"github.com/ethereum/go-ethereum/common" | ||
) | ||
|
||
type Etherman2AuthStore struct { | ||
chainID uint64 | ||
auth map[common.Address]bind.TransactOpts // empty in case of read-only client | ||
} | ||
|
||
func NewEtherman2L1Auth(chainID uint64) (*Etherman2AuthStore, error) { | ||
return &Etherman2AuthStore{ | ||
chainID: chainID, | ||
auth: map[common.Address]bind.TransactOpts{}, | ||
}, nil | ||
} | ||
|
||
// getAuthByAddress tries to get an authorization from the authorizations map | ||
func (etherMan *Etherman2AuthStore) GetAuthByAddress(addr common.Address) (bind.TransactOpts, bool) { | ||
auth, found := etherMan.auth[addr] | ||
return auth, found | ||
} | ||
|
||
// LoadAuthFromKeyStore loads an authorization from a key store file | ||
// Store the authorization in the authorizations map and returns it | ||
func (etherMan *Etherman2AuthStore) LoadAuthFromKeyStore(path, password string) (*bind.TransactOpts, *ecdsa.PrivateKey, error) { | ||
auth, pk, err := newAuthFromKeystore(path, password, etherMan.chainID) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
log.Infof("loaded authorization for address: %v", auth.From.String()) | ||
etherMan.auth[auth.From] = auth | ||
return &auth, pk, nil | ||
} | ||
|
||
// AddOrReplaceAuth adds an authorization or replace an existent one to the same account | ||
func (etherMan *Etherman2AuthStore) AddOrReplaceAuth(auth bind.TransactOpts) error { | ||
log.Infof("added or replaced authorization for address: %v", auth.From.String()) | ||
etherMan.auth[auth.From] = auth | ||
return nil | ||
} | ||
|
||
// newAuthFromKeystore an authorization instance from a keystore file | ||
func newAuthFromKeystore(path, password string, chainID uint64) (bind.TransactOpts, *ecdsa.PrivateKey, error) { | ||
log.Infof("reading key from: %v", path) | ||
key, err := newKeyFromKeystore(path, password) | ||
if err != nil { | ||
return bind.TransactOpts{}, nil, err | ||
} | ||
if key == nil { | ||
return bind.TransactOpts{}, nil, nil | ||
} | ||
auth, err := bind.NewKeyedTransactorWithChainID(key.PrivateKey, new(big.Int).SetUint64(chainID)) | ||
if err != nil { | ||
return bind.TransactOpts{}, nil, err | ||
} | ||
return *auth, key.PrivateKey, nil | ||
} | ||
|
||
// newKeyFromKeystore creates an instance of a keystore key from a keystore file | ||
func newKeyFromKeystore(path, password string) (*keystore.Key, error) { | ||
if path == "" && password == "" { | ||
return nil, nil | ||
} | ||
keystoreEncrypted, err := os.ReadFile(filepath.Clean(path)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
log.Infof("decrypting key from: %v", path) | ||
key, err := keystore.DecryptKey(keystoreEncrypted, password) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return key, 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,58 @@ | ||
package internal | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
"github.com/ethereum/go-ethereum" | ||
) | ||
|
||
type Etherman2ChainReader struct { | ||
L1EthClient interface { | ||
ethereum.ChainReader | ||
ethereum.ChainIDReader | ||
} | ||
} | ||
|
||
/* | ||
func LoadContract[T any](address common.Address, backend bind.ContractBackend, funcConstructor func(common.Address, bind.ContractBackend) (T, error)) (T, error) { | ||
zkevm, err := funcConstructor(address, backend) | ||
if err != nil { | ||
var nilObject T | ||
return nilObject, err | ||
} | ||
return zkevm, nil | ||
} | ||
*/ | ||
|
||
func NewEtherman2ChainReader(l1client interface{}) (*Etherman2ChainReader, error) { | ||
client, ok := l1client.(interface { | ||
ethereum.ChainReader | ||
ethereum.ChainIDReader | ||
}) | ||
if !ok { | ||
return nil, errors.New("l1client does not implement required interfaces") | ||
} | ||
return &Etherman2ChainReader{L1EthClient: client}, nil | ||
} | ||
|
||
func (e *Etherman2ChainReader) LastL1BlockNumber(ctx context.Context) (uint64, error) { | ||
if e == nil || e.L1EthClient == nil { | ||
return 0, nil | ||
} | ||
block, err := e.L1EthClient.BlockByNumber(ctx, nil) | ||
if err != nil { | ||
return 0, err | ||
} | ||
return block.NumberU64(), nil | ||
} | ||
func (e *Etherman2ChainReader) ChainID(ctx context.Context) (uint64, error) { | ||
if e == nil || e.L1EthClient == nil { | ||
return 0, nil | ||
} | ||
id, err := e.L1EthClient.ChainID(ctx) | ||
if err != nil { | ||
return 0, err | ||
} | ||
return id.Uint64(), nil | ||
} |
Oops, something went wrong.