-
Notifications
You must be signed in to change notification settings - Fork 835
Oracle Precompile #1445
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
Oracle Precompile #1445
Changes from all commits
68f34c6
3ee4415
6ab82eb
4ab2837
99274c1
82ff4ef
9c84118
9b96ac9
434e97a
1ec922b
805375a
8d5ad85
e51bf51
208435c
c1a9e11
e01e989
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,6 +173,50 @@ describe("EVM Test", function () { | |
// TODO: Add staking query precompile here | ||
}); | ||
}); | ||
|
||
describe("EVM Oracle Precompile Tester", function () { | ||
const OraclePrecompileContract = '0x0000000000000000000000000000000000001008'; | ||
before(async function() { | ||
const exchangeRatesContent = readDeploymentOutput('oracle_exchange_rates.json'); | ||
const twapsContent = readDeploymentOutput('oracle_twaps.json'); | ||
|
||
exchangeRatesJSON = JSON.parse(exchangeRatesContent).denom_oracle_exchange_rate_pairs; | ||
twapsJSON = JSON.parse(twapsContent).oracle_twaps; | ||
|
||
const [signer, _] = await ethers.getSigners(); | ||
owner = await signer.getAddress(); | ||
|
||
const contractABIPath = path.join(__dirname, '../../precompiles/oracle/abi.json'); | ||
const contractABI = require(contractABIPath); | ||
// Get a contract instance | ||
oracle = new ethers.Contract(OraclePrecompileContract, contractABI, signer); | ||
}); | ||
|
||
it("Oracle Exchange Rates", async function () { | ||
const exchangeRates = await oracle.getExchangeRates(); | ||
const exchangeRatesLen = exchangeRatesJSON.length; | ||
expect(exchangeRates.length).to.equal(exchangeRatesLen); | ||
|
||
for (let i = 0; i < exchangeRatesLen; i++) { | ||
expect(exchangeRates[i].denom).to.equal(exchangeRatesJSON[i].denom); | ||
expect(exchangeRates[i].oracleExchangeRateVal.exchangeRate).to.be.a('string').and.to.not.be.empty; | ||
expect(exchangeRates[i].oracleExchangeRateVal.exchangeRate).to.be.a('string').and.to.not.be.empty; | ||
expect(exchangeRates[i].oracleExchangeRateVal.lastUpdateTimestamp).to.exist.and.to.be.gt(0); | ||
} | ||
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 we check that the other values in 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. @jewei1997 yea, take a look at this comment above about this #1445 (comment) |
||
}); | ||
|
||
it("Oracle Twaps", async function () { | ||
const twaps = await oracle.getOracleTwaps(3600); | ||
const twapsLen = twapsJSON.length | ||
expect(twaps.length).to.equal(twapsLen); | ||
|
||
for (let i = 0; i < twapsLen; i++) { | ||
expect(twaps[i].denom).to.equal(twapsJSON[i].denom); | ||
expect(twaps[i].twap).to.be.a('string').and.to.not.be.empty; | ||
expect(twaps[i].lookbackSeconds).to.exist.and.to.be.gt(0); | ||
} | ||
Kbhat1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/bin/bash | ||
|
||
# This script is used to query oracle exchange rates and twap | ||
seid q oracle exchange-rates -o json > contracts/oracle_exchange_rates.json | ||
seid q oracle twaps 3600 -o json > contracts/oracle_twaps.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
address constant ORACLE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001008; | ||
|
||
IOracle constant ORACLE_CONTRACT = IOracle(ORACLE_PRECOMPILE_ADDRESS); | ||
|
||
interface IOracle { | ||
// Queries | ||
function getExchangeRates() external view returns (DenomOracleExchangeRatePair[] memory); | ||
function getOracleTwaps(uint64 lookback_seconds) external view returns (OracleTwap[] memory); | ||
|
||
// Structs | ||
struct OracleExchangeRate { | ||
string exchangeRate; | ||
string lastUpdate; | ||
int64 lastUpdateTimestamp; | ||
} | ||
|
||
struct DenomOracleExchangeRatePair { | ||
string denom; | ||
OracleExchangeRate oracleExchangeRateVal; | ||
} | ||
|
||
struct OracleTwap { | ||
string denom; | ||
string twap; | ||
int64 lookbackSeconds; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[{"inputs":[],"name":"getExchangeRates","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"components":[{"internalType":"string","name":"exchangeRate","type":"string"},{"internalType":"string","name":"lastUpdate","type":"string"},{"internalType":"int64","name":"lastUpdateTimestamp","type":"int64"}],"internalType":"struct IOracle.OracleExchangeRate","name":"oracleExchangeRateVal","type":"tuple"}],"internalType":"struct IOracle.DenomOracleExchangeRatePair[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"lookback_seconds","type":"uint64"}],"name":"getOracleTwaps","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"twap","type":"string"},{"internalType":"int64","name":"lookbackSeconds","type":"int64"}],"internalType":"struct IOracle.OracleTwap[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package oracle | ||
|
||
import ( | ||
"bytes" | ||
"embed" | ||
"math/big" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
"github.com/ethereum/go-ethereum/accounts/abi" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/vm" | ||
pcommon "github.com/sei-protocol/sei-chain/precompiles/common" | ||
"github.com/sei-protocol/sei-chain/x/oracle/types" | ||
) | ||
|
||
const ( | ||
GetExchangeRatesMethod = "getExchangeRates" | ||
GetOracleTwapsMethod = "getOracleTwaps" | ||
) | ||
|
||
const ( | ||
OracleAddress = "0x0000000000000000000000000000000000001008" | ||
) | ||
|
||
var _ vm.PrecompiledContract = &Precompile{} | ||
|
||
// Embed abi json file to the executable binary. Needed when importing as dependency. | ||
// | ||
//go:embed abi.json | ||
var f embed.FS | ||
|
||
func GetABI() abi.ABI { | ||
abiBz, err := f.ReadFile("abi.json") | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
newAbi, err := abi.JSON(bytes.NewReader(abiBz)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return newAbi | ||
} | ||
|
||
type Precompile struct { | ||
pcommon.Precompile | ||
evmKeeper pcommon.EVMKeeper | ||
oracleKeeper pcommon.OracleKeeper | ||
address common.Address | ||
|
||
GetExchangeRatesId []byte | ||
GetOracleTwapsId []byte | ||
} | ||
|
||
// Define types which deviate slightly from cosmos types (ExchangeRate string vs sdk.Dec) | ||
type OracleExchangeRate struct { | ||
ExchangeRate string `json:"exchangeRate"` | ||
LastUpdate string `json:"lastUpdate"` | ||
LastUpdateTimestamp int64 `json:"lastUpdateTimestamp"` | ||
} | ||
|
||
type DenomOracleExchangeRatePair struct { | ||
Denom string `json:"denom"` | ||
OracleExchangeRateVal OracleExchangeRate `json:"oracleExchangeRateVal"` | ||
} | ||
|
||
type OracleTwap struct { | ||
Denom string `json:"denom"` | ||
Twap string `json:"twap"` | ||
LookbackSeconds int64 `json:"lookbackSeconds"` | ||
} | ||
|
||
func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { | ||
newAbi := GetABI() | ||
|
||
p := &Precompile{ | ||
Precompile: pcommon.Precompile{ABI: newAbi}, | ||
evmKeeper: evmKeeper, | ||
address: common.HexToAddress(OracleAddress), | ||
oracleKeeper: oracleKeeper, | ||
} | ||
|
||
for name, m := range newAbi.Methods { | ||
switch name { | ||
case GetExchangeRatesMethod: | ||
p.GetExchangeRatesId = m.ID | ||
case GetOracleTwapsMethod: | ||
p.GetOracleTwapsId = m.ID | ||
} | ||
} | ||
Comment on lines
+84
to
+91
Check warningCode scanning / CodeQL Iteration over map
Iteration over map may be a possible source of non-determinism
|
||
|
||
return p, nil | ||
} | ||
|
||
// RequiredGas returns the required bare minimum gas to execute the precompile. | ||
func (p Precompile) RequiredGas(input []byte) uint64 { | ||
methodID := input[:4] | ||
|
||
method, err := p.ABI.MethodById(methodID) | ||
if err != nil { | ||
// This should never happen since this method is going to fail during Run | ||
Kbhat1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return 0 | ||
} | ||
|
||
return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) | ||
} | ||
|
||
func (p Precompile) Address() common.Address { | ||
return p.address | ||
} | ||
|
||
func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, value *big.Int) (bz []byte, err error) { | ||
ctx, method, args, err := p.Prepare(evm, input) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
switch method.Name { | ||
case GetExchangeRatesMethod: | ||
return p.getExchangeRates(ctx, method, args, value) | ||
case GetOracleTwapsMethod: | ||
return p.getOracleTwaps(ctx, method, args, value) | ||
} | ||
Kbhat1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return | ||
} | ||
|
||
func (p Precompile) getExchangeRates(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { | ||
pcommon.AssertNonPayable(value) | ||
Kbhat1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pcommon.AssertArgsLength(args, 0) | ||
exchangeRates := []DenomOracleExchangeRatePair{} | ||
p.oracleKeeper.IterateBaseExchangeRates(ctx, func(denom string, rate types.OracleExchangeRate) (stop bool) { | ||
exchangeRates = append(exchangeRates, DenomOracleExchangeRatePair{Denom: denom, OracleExchangeRateVal: OracleExchangeRate{ExchangeRate: rate.ExchangeRate.String(), LastUpdate: rate.LastUpdate.String(), LastUpdateTimestamp: rate.LastUpdateTimestamp}}) | ||
return false | ||
}) | ||
|
||
return method.Outputs.Pack(exchangeRates) | ||
} | ||
|
||
func (p Precompile) getOracleTwaps(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { | ||
pcommon.AssertNonPayable(value) | ||
pcommon.AssertArgsLength(args, 1) | ||
lookbackSeconds := args[0].(uint64) | ||
twaps, err := p.oracleKeeper.CalculateTwaps(ctx, lookbackSeconds) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// Convert twap to string | ||
oracleTwaps := make([]OracleTwap, 0, len(twaps)) | ||
for _, twap := range twaps { | ||
oracleTwaps = append(oracleTwaps, OracleTwap{Denom: twap.Denom, Twap: twap.Twap.String(), LookbackSeconds: twap.LookbackSeconds}) | ||
} | ||
return method.Outputs.Pack(oracleTwaps) | ||
} | ||
|
||
func (Precompile) IsTransaction(string) bool { | ||
return false | ||
} |
Uh oh!
There was an error while loading. Please reload this page.