-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
8b4686e
commit 528e80a
Showing
10 changed files
with
233 additions
and
13 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"chainlink": patch | ||
--- | ||
|
||
Adds new custom calldata DA oracle #wip |
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
159 changes: 159 additions & 0 deletions
159
core/chains/evm/gas/rollups/custom_calldata_l1_oracle.go
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,159 @@ | ||
package rollups | ||
|
||
import ( | ||
"context" | ||
"encoding/hex" | ||
"fmt" | ||
"math/big" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum" | ||
"github.com/smartcontractkit/chainlink-common/pkg/logger" | ||
"github.com/smartcontractkit/chainlink-common/pkg/services" | ||
|
||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" | ||
evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" | ||
evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" | ||
) | ||
|
||
type customCalldataDAOracle struct { | ||
services.StateMachine | ||
client daOracleClient | ||
pollPeriod time.Duration | ||
logger logger.SugaredLogger | ||
|
||
daOracleConfig evmconfig.DAOracle | ||
daGasPriceMu sync.RWMutex | ||
daGasPrice priceEntry | ||
|
||
chInitialized chan struct{} | ||
chStop services.StopChan | ||
chDone chan struct{} | ||
} | ||
|
||
// NewCustomCalldataDAOracle creates a new custom calldata DA oracle. The CustomCalldataDAOracle fetches gas price from | ||
// whatever function is specified in the DAOracle's CustomGasPriceCalldata field. This allows for more flexibility when | ||
// chains have custom DA gas calculation methods. | ||
func NewCustomCalldataDAOracle(lggr logger.Logger, ethClient daOracleClient, daOracleConfig evmconfig.DAOracle) *customCalldataDAOracle { | ||
return &customCalldataDAOracle{ | ||
client: ethClient, | ||
pollPeriod: PollPeriod, | ||
logger: logger.Sugared(logger.Named(lggr, fmt.Sprintf("CustomCalldataDAOracle(%s)", daOracleConfig.OracleType()))), | ||
|
||
daOracleConfig: daOracleConfig, | ||
|
||
chInitialized: make(chan struct{}), | ||
chStop: make(chan struct{}), | ||
chDone: make(chan struct{}), | ||
} | ||
} | ||
|
||
func (o *customCalldataDAOracle) Name() string { | ||
return o.logger.Name() | ||
} | ||
|
||
func (o *customCalldataDAOracle) Start(_ context.Context) error { | ||
return o.StartOnce(o.Name(), func() error { | ||
go o.run() | ||
<-o.chInitialized | ||
return nil | ||
}) | ||
} | ||
|
||
func (o *customCalldataDAOracle) Close() error { | ||
return o.StopOnce(o.Name(), func() error { | ||
close(o.chStop) | ||
<-o.chDone | ||
return nil | ||
}) | ||
} | ||
|
||
func (o *customCalldataDAOracle) HealthReport() map[string]error { | ||
return map[string]error{o.Name(): o.Healthy()} | ||
} | ||
|
||
func (o *customCalldataDAOracle) run() { | ||
defer close(o.chDone) | ||
|
||
o.refresh() | ||
close(o.chInitialized) | ||
|
||
t := services.TickerConfig{ | ||
Initial: o.pollPeriod, | ||
JitterPct: services.DefaultJitter, | ||
}.NewTicker(o.pollPeriod) | ||
defer t.Stop() | ||
|
||
for { | ||
select { | ||
case <-o.chStop: | ||
return | ||
case <-t.C: | ||
o.refresh() | ||
} | ||
} | ||
} | ||
|
||
func (o *customCalldataDAOracle) refresh() { | ||
err := o.refreshWithError() | ||
if err != nil { | ||
o.logger.Criticalw("Failed to refresh gas price", "err", err) | ||
o.SvcErrBuffer.Append(err) | ||
} | ||
} | ||
|
||
func (o *customCalldataDAOracle) refreshWithError() error { | ||
ctx, cancel := o.chStop.CtxCancel(evmclient.ContextWithDefaultTimeout()) | ||
defer cancel() | ||
|
||
price, err := o.getCustomCalldataGasPrice(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
o.daGasPriceMu.Lock() | ||
defer o.daGasPriceMu.Unlock() | ||
o.daGasPrice = priceEntry{price: assets.NewWei(price), timestamp: time.Now()} | ||
return nil | ||
} | ||
|
||
func (o *customCalldataDAOracle) GasPrice(_ context.Context) (daGasPrice *assets.Wei, err error) { | ||
var timestamp time.Time | ||
ok := o.IfStarted(func() { | ||
o.daGasPriceMu.RLock() | ||
daGasPrice = o.daGasPrice.price | ||
timestamp = o.daGasPrice.timestamp | ||
o.daGasPriceMu.RUnlock() | ||
}) | ||
if !ok { | ||
return daGasPrice, fmt.Errorf("DAGasOracle is not started; cannot estimate gas") | ||
} | ||
if daGasPrice == nil { | ||
return daGasPrice, fmt.Errorf("failed to get DA gas price; gas price not set") | ||
} | ||
// Validate the price has been updated within the pollPeriod * 2 | ||
// Allowing double the poll period before declaring the price stale to give ample time for the refresh to process | ||
if time.Since(timestamp) > o.pollPeriod*2 { | ||
return daGasPrice, fmt.Errorf("gas price is stale") | ||
} | ||
return | ||
} | ||
|
||
func (o *customCalldataDAOracle) getCustomCalldataGasPrice(ctx context.Context) (*big.Int, error) { | ||
daOracleAddress := o.daOracleConfig.OracleAddress().Address() | ||
calldata := strings.TrimPrefix(o.daOracleConfig.CustomGasPriceCalldata(), "0x") | ||
calldataBytes, err := hex.DecodeString(calldata) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode custom fee method calldata: %w", err) | ||
} | ||
b, err := o.client.CallContract(ctx, ethereum.CallMsg{ | ||
To: &daOracleAddress, | ||
Data: calldataBytes, | ||
}, nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("custom fee method call failed: %w", err) | ||
} | ||
return new(big.Int).SetBytes(b), nil | ||
} |
55 changes: 55 additions & 0 deletions
55
core/chains/evm/gas/rollups/custom_calldata_l1_oracle_test.go
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,55 @@ | ||
package rollups | ||
|
||
import ( | ||
"math/big" | ||
"testing" | ||
|
||
"github.com/ethereum/go-ethereum" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests" | ||
|
||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml/daoracle" | ||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" | ||
|
||
"github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/smartcontractkit/chainlink-common/pkg/logger" | ||
) | ||
|
||
func TestOPL1Oracle_CalculateCustomCalldataGasPrice(t *testing.T) { | ||
oracleAddress := common.HexToAddress("0x0000000000000000000000000000000044433322").String() | ||
|
||
t.Parallel() | ||
|
||
t.Run("correctly fetches gas price if chain has custom calldata", func(t *testing.T) { | ||
ethClient := mocks.NewL1OracleClient(t) | ||
expectedPriceHex := "0x0000000000000000000000000000000000000000000000000000000000000032" | ||
|
||
daOracle := CreateTestDAOracle(t, daoracle.OPStack, oracleAddress, "0x0000000000000000000000000000000000001234") | ||
oracle := NewCustomCalldataDAOracle(logger.Test(t), ethClient, daOracle) | ||
|
||
ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) { | ||
callMsg := args.Get(1).(ethereum.CallMsg) | ||
blockNumber := args.Get(2).(*big.Int) | ||
require.NotNil(t, callMsg.To) | ||
require.Equal(t, oracleAddress, callMsg.To.String()) | ||
require.Nil(t, blockNumber) | ||
}).Return(hexutil.MustDecode(expectedPriceHex), nil).Once() | ||
|
||
price, err := oracle.getCustomCalldataGasPrice(tests.Context(t)) | ||
require.NoError(t, err) | ||
require.Equal(t, big.NewInt(50), price) | ||
}) | ||
|
||
t.Run("throws error if custom calldata fails to decode", func(t *testing.T) { | ||
ethClient := mocks.NewL1OracleClient(t) | ||
|
||
daOracle := CreateTestDAOracle(t, daoracle.OPStack, oracleAddress, "0xblahblahblah") | ||
oracle := NewCustomCalldataDAOracle(logger.Test(t), ethClient, daOracle) | ||
|
||
_, err := oracle.getCustomCalldataGasPrice(tests.Context(t)) | ||
require.Error(t, err) | ||
}) | ||
} |
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
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