865 lines
23 KiB
TypeScript
865 lines
23 KiB
TypeScript
import Web3 from "web3";
|
|
import PythInterfaceAbi from "@pythnetwork/pyth-sdk-solidity/abis/IPyth.json";
|
|
import EntropyAbi from "@pythnetwork/entropy-sdk-solidity/abis/IEntropy.json";
|
|
import { PriceFeedContract, PrivateKey, Storable } from "../base";
|
|
import { Chain, EvmChain } from "../chains";
|
|
import { DataSource, EvmExecute } from "xc_admin_common";
|
|
import { WormholeContract } from "./wormhole";
|
|
import { TokenQty } from "../token";
|
|
|
|
// Just to make sure tx gas limit is enough
|
|
const EXTENDED_ENTROPY_ABI = [
|
|
{
|
|
inputs: [],
|
|
name: "acceptOwnership",
|
|
outputs: [],
|
|
stateMutability: "nonpayable",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "acceptAdmin",
|
|
outputs: [],
|
|
stateMutability: "nonpayable",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "owner",
|
|
outputs: [
|
|
{
|
|
internalType: "address",
|
|
name: "",
|
|
type: "address",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "pendingOwner",
|
|
outputs: [
|
|
{
|
|
internalType: "address",
|
|
name: "",
|
|
type: "address",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "version",
|
|
outputs: [
|
|
{
|
|
internalType: "string",
|
|
name: "",
|
|
type: "string",
|
|
},
|
|
],
|
|
stateMutability: "pure",
|
|
type: "function",
|
|
},
|
|
...EntropyAbi,
|
|
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
const EXTENDED_PYTH_ABI = [
|
|
{
|
|
inputs: [],
|
|
name: "wormhole",
|
|
outputs: [
|
|
{
|
|
internalType: "contract IWormhole",
|
|
name: "",
|
|
type: "address",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "governanceDataSource",
|
|
outputs: [
|
|
{
|
|
components: [
|
|
{
|
|
internalType: "uint16",
|
|
name: "chainId",
|
|
type: "uint16",
|
|
},
|
|
{
|
|
internalType: "bytes32",
|
|
name: "emitterAddress",
|
|
type: "bytes32",
|
|
},
|
|
],
|
|
internalType: "struct PythInternalStructs.DataSource",
|
|
name: "",
|
|
type: "tuple",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [
|
|
{
|
|
internalType: "bytes",
|
|
name: "encodedVM",
|
|
type: "bytes",
|
|
},
|
|
],
|
|
name: "executeGovernanceInstruction",
|
|
outputs: [],
|
|
stateMutability: "nonpayable",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "version",
|
|
outputs: [{ internalType: "string", name: "", type: "string" }],
|
|
stateMutability: "pure",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "singleUpdateFeeInWei",
|
|
outputs: [
|
|
{
|
|
internalType: "uint256",
|
|
name: "",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "validDataSources",
|
|
outputs: [
|
|
{
|
|
components: [
|
|
{
|
|
internalType: "uint16",
|
|
name: "chainId",
|
|
type: "uint16",
|
|
},
|
|
{
|
|
internalType: "bytes32",
|
|
name: "emitterAddress",
|
|
type: "bytes32",
|
|
},
|
|
],
|
|
internalType: "struct PythInternalStructs.DataSource[]",
|
|
name: "",
|
|
type: "tuple[]",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
constant: true,
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "lastExecutedGovernanceSequence",
|
|
outputs: [
|
|
{
|
|
internalType: "uint64",
|
|
name: "",
|
|
type: "uint64",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [
|
|
{
|
|
internalType: "bytes32",
|
|
name: "id",
|
|
type: "bytes32",
|
|
},
|
|
],
|
|
name: "priceFeedExists",
|
|
outputs: [
|
|
{
|
|
internalType: "bool",
|
|
name: "",
|
|
type: "bool",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
...PythInterfaceAbi,
|
|
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
const WORMHOLE_ABI = [
|
|
{
|
|
inputs: [],
|
|
name: "getCurrentGuardianSetIndex",
|
|
outputs: [
|
|
{
|
|
internalType: "uint32",
|
|
name: "",
|
|
type: "uint32",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "chainId",
|
|
outputs: [
|
|
{
|
|
internalType: "uint16",
|
|
name: "",
|
|
type: "uint16",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [
|
|
{
|
|
internalType: "uint32",
|
|
name: "index",
|
|
type: "uint32",
|
|
},
|
|
],
|
|
name: "getGuardianSet",
|
|
outputs: [
|
|
{
|
|
components: [
|
|
{
|
|
internalType: "address[]",
|
|
name: "keys",
|
|
type: "address[]",
|
|
},
|
|
{
|
|
internalType: "uint32",
|
|
name: "expirationTime",
|
|
type: "uint32",
|
|
},
|
|
],
|
|
internalType: "struct Structs.GuardianSet",
|
|
name: "",
|
|
type: "tuple",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [
|
|
{
|
|
internalType: "bytes",
|
|
name: "_vm",
|
|
type: "bytes",
|
|
},
|
|
],
|
|
name: "submitNewGuardianSet",
|
|
outputs: [],
|
|
stateMutability: "nonpayable",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "messageFee",
|
|
outputs: [
|
|
{
|
|
internalType: "uint256",
|
|
name: "",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
const EXECUTOR_ABI = [
|
|
{
|
|
inputs: [
|
|
{
|
|
internalType: "bytes",
|
|
name: "encodedVm",
|
|
type: "bytes",
|
|
},
|
|
],
|
|
name: "execute",
|
|
outputs: [
|
|
{
|
|
internalType: "bytes",
|
|
name: "response",
|
|
type: "bytes",
|
|
},
|
|
],
|
|
stateMutability: "nonpayable",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "getOwnerChainId",
|
|
outputs: [
|
|
{
|
|
internalType: "uint64",
|
|
name: "",
|
|
type: "uint64",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "getOwnerEmitterAddress",
|
|
outputs: [
|
|
{
|
|
internalType: "bytes32",
|
|
name: "",
|
|
type: "bytes32",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "getLastExecutedSequence",
|
|
outputs: [
|
|
{
|
|
internalType: "uint64",
|
|
name: "",
|
|
type: "uint64",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
inputs: [],
|
|
name: "owner",
|
|
outputs: [
|
|
{
|
|
internalType: "address",
|
|
name: "",
|
|
type: "address",
|
|
},
|
|
],
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
export class WormholeEvmContract extends WormholeContract {
|
|
constructor(public chain: EvmChain, public address: string) {
|
|
super();
|
|
}
|
|
getContract() {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
return new web3.eth.Contract(WORMHOLE_ABI, this.address);
|
|
}
|
|
|
|
async getCurrentGuardianSetIndex(): Promise<number> {
|
|
const wormholeContract = this.getContract();
|
|
return Number(
|
|
await wormholeContract.methods.getCurrentGuardianSetIndex().call()
|
|
);
|
|
}
|
|
|
|
async getChainId(): Promise<number> {
|
|
const wormholeContract = this.getContract();
|
|
return Number(await wormholeContract.methods.chainId().call());
|
|
}
|
|
|
|
/**
|
|
* Returns an array of guardian addresses used for VAA verification in this contract
|
|
*/
|
|
async getGuardianSet(): Promise<string[]> {
|
|
const wormholeContract = this.getContract();
|
|
const currentIndex = await this.getCurrentGuardianSetIndex();
|
|
const [currentSet] = await wormholeContract.methods
|
|
.getGuardianSet(currentIndex)
|
|
.call();
|
|
return currentSet;
|
|
}
|
|
|
|
async upgradeGuardianSets(senderPrivateKey: PrivateKey, vaa: Buffer) {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
|
const wormholeContract = new web3.eth.Contract(WORMHOLE_ABI, this.address);
|
|
const transactionObject = wormholeContract.methods.submitNewGuardianSet(
|
|
"0x" + vaa.toString("hex")
|
|
);
|
|
const result = await this.chain.estiamteAndSendTransaction(
|
|
transactionObject,
|
|
{ from: address }
|
|
);
|
|
return { id: result.transactionHash, info: result };
|
|
}
|
|
}
|
|
|
|
interface EntropyProviderInfo {
|
|
feeInWei: string;
|
|
accruedFeesInWei: string;
|
|
originalCommitment: string;
|
|
originalCommitmentSequenceNumber: string;
|
|
commitmentMetadata: string;
|
|
uri: string;
|
|
endSequenceNumber: string;
|
|
sequenceNumber: string;
|
|
currentCommitment: string;
|
|
currentCommitmentSequenceNumber: string;
|
|
}
|
|
|
|
export class EvmEntropyContract extends Storable {
|
|
static type = "EvmEntropyContract";
|
|
|
|
constructor(public chain: EvmChain, public address: string) {
|
|
super();
|
|
}
|
|
|
|
getId(): string {
|
|
return `${this.chain.getId()}_${this.address}`;
|
|
}
|
|
|
|
getChain(): EvmChain {
|
|
return this.chain;
|
|
}
|
|
|
|
getType(): string {
|
|
return EvmEntropyContract.type;
|
|
}
|
|
|
|
async getVersion(): Promise<string> {
|
|
const contract = this.getContract();
|
|
return contract.methods.version().call();
|
|
}
|
|
|
|
static fromJson(
|
|
chain: Chain,
|
|
parsed: { type: string; address: string }
|
|
): EvmEntropyContract {
|
|
if (parsed.type !== EvmEntropyContract.type)
|
|
throw new Error("Invalid type");
|
|
if (!(chain instanceof EvmChain))
|
|
throw new Error(`Wrong chain type ${chain}`);
|
|
return new EvmEntropyContract(chain, parsed.address);
|
|
}
|
|
|
|
// Generate a payload for the given executor address and calldata.
|
|
// `executor` and `calldata` should be hex strings.
|
|
generateExecutorPayload(
|
|
executor: string,
|
|
callAddress: string,
|
|
calldata: string
|
|
) {
|
|
return new EvmExecute(
|
|
this.chain.wormholeChainName,
|
|
executor.replace("0x", ""),
|
|
callAddress.replace("0x", ""),
|
|
0n,
|
|
Buffer.from(calldata.replace("0x", ""), "hex")
|
|
).encode();
|
|
}
|
|
|
|
// Generates a payload for the newAdmin to call acceptAdmin on the entropy contracts
|
|
generateAcceptAdminPayload(newAdmin: string): Buffer {
|
|
const contract = this.getContract();
|
|
const data = contract.methods.acceptAdmin().encodeABI();
|
|
return this.generateExecutorPayload(newAdmin, this.address, data);
|
|
}
|
|
|
|
// Generates a payload for newOwner to call acceptOwnership on the entropy contracts
|
|
generateAcceptOwnershipPayload(newOwner: string): Buffer {
|
|
const contract = this.getContract();
|
|
const data = contract.methods.acceptOwnership().encodeABI();
|
|
return this.generateExecutorPayload(newOwner, this.address, data);
|
|
}
|
|
|
|
// Generates a payload to upgrade the executor contract, the owner of entropy contracts
|
|
async generateUpgradeExecutorContractsPayload(
|
|
newImplementation: string
|
|
): Promise<Buffer> {
|
|
// Executor contract is the owner of entropy contract
|
|
const executorAddr = await this.getOwner();
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
const executor = new web3.eth.Contract(EXECUTOR_ABI, executorAddr);
|
|
const data = executor.methods.upgradeTo(newImplementation).encodeABI();
|
|
return this.generateExecutorPayload(executorAddr, executorAddr, data);
|
|
}
|
|
|
|
async getOwner(): Promise<string> {
|
|
const contract = this.getContract();
|
|
return contract.methods.owner().call();
|
|
}
|
|
|
|
async getExecutorContract(): Promise<EvmExecutorContract> {
|
|
const owner = await this.getOwner();
|
|
return new EvmExecutorContract(this.chain, owner);
|
|
}
|
|
|
|
async getPendingOwner(): Promise<string> {
|
|
const contract = this.getContract();
|
|
return contract.methods.pendingOwner().call();
|
|
}
|
|
|
|
toJson() {
|
|
return {
|
|
chain: this.chain.getId(),
|
|
address: this.address,
|
|
type: EvmEntropyContract.type,
|
|
};
|
|
}
|
|
|
|
getContract() {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
return new web3.eth.Contract(EXTENDED_ENTROPY_ABI, this.address);
|
|
}
|
|
|
|
async getDefaultProvider(): Promise<string> {
|
|
const contract = this.getContract();
|
|
return await contract.methods.getDefaultProvider().call();
|
|
}
|
|
|
|
async getProviderInfo(address: string): Promise<EntropyProviderInfo> {
|
|
const contract = this.getContract();
|
|
const info: EntropyProviderInfo = await contract.methods
|
|
.getProviderInfo(address)
|
|
.call();
|
|
return {
|
|
...info,
|
|
uri: Web3.utils.toAscii(info.uri),
|
|
};
|
|
}
|
|
|
|
generateUserRandomNumber() {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
return web3.utils.randomHex(32);
|
|
}
|
|
|
|
async requestRandomness(
|
|
userRandomNumber: string,
|
|
provider: string,
|
|
senderPrivateKey: PrivateKey
|
|
) {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
const userCommitment = web3.utils.keccak256(userRandomNumber);
|
|
const contract = new web3.eth.Contract(EXTENDED_ENTROPY_ABI, this.address);
|
|
const fee = await contract.methods.getFee(provider).call();
|
|
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
|
const useBlockHash = false;
|
|
const transactionObject = contract.methods.request(
|
|
provider,
|
|
userCommitment,
|
|
useBlockHash
|
|
);
|
|
return this.chain.estiamteAndSendTransaction(transactionObject, {
|
|
from: address,
|
|
value: fee,
|
|
});
|
|
}
|
|
|
|
async revealRandomness(
|
|
userRevelation: string,
|
|
providerRevelation: string,
|
|
provider: string,
|
|
sequenceNumber: string,
|
|
senderPrivateKey: PrivateKey
|
|
) {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
const contract = new web3.eth.Contract(EXTENDED_ENTROPY_ABI, this.address);
|
|
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
|
const transactionObject = contract.methods.reveal(
|
|
provider,
|
|
sequenceNumber,
|
|
userRevelation,
|
|
providerRevelation
|
|
);
|
|
return this.chain.estiamteAndSendTransaction(transactionObject, {
|
|
from: address,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class EvmExecutorContract {
|
|
constructor(public chain: EvmChain, public address: string) {}
|
|
|
|
getId(): string {
|
|
return `${this.chain.getId()}_${this.address}`;
|
|
}
|
|
|
|
async getWormholeContract(): Promise<WormholeEvmContract> {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
//Unfortunately, there is no public method to get the wormhole address
|
|
//Found 251 by using `forge build --extra-output storageLayout` and finding the slot for the wormhole variable.
|
|
let address = await web3.eth.getStorageAt(this.address, 251);
|
|
address = "0x" + address.slice(26);
|
|
return new WormholeEvmContract(this.chain, address);
|
|
}
|
|
|
|
getContract() {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
return new web3.eth.Contract(EXECUTOR_ABI, this.address);
|
|
}
|
|
|
|
async getLastExecutedGovernanceSequence() {
|
|
return await this.getContract().methods.getLastExecutedSequence().call();
|
|
}
|
|
|
|
async getGovernanceDataSource(): Promise<DataSource> {
|
|
const executorContract = this.getContract();
|
|
const ownerEmitterAddress = await executorContract.methods
|
|
.getOwnerEmitterAddress()
|
|
.call();
|
|
const ownerEmitterChainid = await executorContract.methods
|
|
.getOwnerChainId()
|
|
.call();
|
|
return {
|
|
emitterChain: Number(ownerEmitterChainid),
|
|
emitterAddress: ownerEmitterAddress.replace("0x", ""),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns the owner of the executor contract, this should always be the contract address itself
|
|
*/
|
|
async getOwner(): Promise<string> {
|
|
const contract = this.getContract();
|
|
return contract.methods.owner().call();
|
|
}
|
|
|
|
async executeGovernanceInstruction(
|
|
senderPrivateKey: PrivateKey,
|
|
vaa: Buffer
|
|
) {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
|
const executorContract = new web3.eth.Contract(EXECUTOR_ABI, this.address);
|
|
const transactionObject = executorContract.methods.execute(
|
|
"0x" + vaa.toString("hex")
|
|
);
|
|
const result = await this.chain.estiamteAndSendTransaction(
|
|
transactionObject,
|
|
{ from: address }
|
|
);
|
|
return { id: result.transactionHash, info: result };
|
|
}
|
|
}
|
|
|
|
export class EvmPriceFeedContract extends PriceFeedContract {
|
|
static type = "EvmPriceFeedContract";
|
|
|
|
constructor(public chain: EvmChain, public address: string) {
|
|
super();
|
|
}
|
|
|
|
static fromJson(
|
|
chain: Chain,
|
|
parsed: { type: string; address: string }
|
|
): EvmPriceFeedContract {
|
|
if (parsed.type !== EvmPriceFeedContract.type)
|
|
throw new Error("Invalid type");
|
|
if (!(chain instanceof EvmChain))
|
|
throw new Error(`Wrong chain type ${chain}`);
|
|
return new EvmPriceFeedContract(chain, parsed.address);
|
|
}
|
|
|
|
getId(): string {
|
|
return `${this.chain.getId()}_${this.address}`;
|
|
}
|
|
|
|
getType(): string {
|
|
return EvmPriceFeedContract.type;
|
|
}
|
|
|
|
async getVersion(): Promise<string> {
|
|
const pythContract = this.getContract();
|
|
const result = await pythContract.methods.version().call();
|
|
return result;
|
|
}
|
|
|
|
getContract() {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
|
return pythContract;
|
|
}
|
|
|
|
/**
|
|
* Returns the bytecode of the contract in hex format
|
|
*/
|
|
async getCode(): Promise<string> {
|
|
// TODO: handle proxy contracts
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
return web3.eth.getCode(this.address);
|
|
}
|
|
|
|
async getImplementationAddress(): Promise<string> {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) according to EIP-1967
|
|
const storagePosition =
|
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
|
let address = await web3.eth.getStorageAt(this.address, storagePosition);
|
|
address = "0x" + address.slice(26);
|
|
return address;
|
|
}
|
|
|
|
/**
|
|
* Returns the keccak256 digest of the contract bytecode after replacing any occurrences of the contract addr in
|
|
* the bytecode with 0.The bytecode stores the deployment address as an immutable variable.
|
|
* This behavior is inherited from OpenZeppelin's implementation of UUPSUpgradeable contract.
|
|
* You can read more about verification with immutable variables here:
|
|
* https://docs.sourcify.dev/docs/immutables/
|
|
* This function can be used to verify that the contract code is the same on all chains and matches
|
|
* with the deployedCode property generated by truffle builds
|
|
*/
|
|
async getCodeDigestWithoutAddress(): Promise<string> {
|
|
const code = await this.getCode();
|
|
const strippedCode = code.replaceAll(
|
|
this.address.toLowerCase().replace("0x", ""),
|
|
"0000000000000000000000000000000000000000"
|
|
);
|
|
return Web3.utils.keccak256(strippedCode);
|
|
}
|
|
|
|
async getTotalFee(): Promise<TokenQty> {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
const amount = BigInt(await web3.eth.getBalance(this.address));
|
|
return {
|
|
amount,
|
|
denom: this.chain.getNativeToken(),
|
|
};
|
|
}
|
|
|
|
async getLastExecutedGovernanceSequence() {
|
|
const pythContract = await this.getContract();
|
|
return Number(
|
|
await pythContract.methods.lastExecutedGovernanceSequence().call()
|
|
);
|
|
}
|
|
|
|
async getPriceFeed(feedId: string) {
|
|
const pythContract = this.getContract();
|
|
const feed = "0x" + feedId;
|
|
const exists = await pythContract.methods.priceFeedExists(feed).call();
|
|
if (!exists) {
|
|
return undefined;
|
|
}
|
|
const [price, conf, expo, publishTime] = await pythContract.methods
|
|
.getPriceUnsafe(feed)
|
|
.call();
|
|
|
|
const [emaPrice, emaConf, emaExpo, emaPublishTime] =
|
|
await pythContract.methods.getEmaPriceUnsafe(feed).call();
|
|
return {
|
|
price: { price, conf, expo, publishTime },
|
|
emaPrice: {
|
|
price: emaPrice,
|
|
conf: emaConf,
|
|
expo: emaExpo,
|
|
publishTime: emaPublishTime,
|
|
},
|
|
};
|
|
}
|
|
|
|
async getValidTimePeriod() {
|
|
const pythContract = this.getContract();
|
|
const result = await pythContract.methods.getValidTimePeriod().call();
|
|
return Number(result);
|
|
}
|
|
|
|
/**
|
|
* Returns the wormhole contract which is being used for VAA verification
|
|
*/
|
|
async getWormholeContract(): Promise<WormholeEvmContract> {
|
|
const pythContract = this.getContract();
|
|
const address = await pythContract.methods.wormhole().call();
|
|
return new WormholeEvmContract(this.chain, address);
|
|
}
|
|
|
|
async getBaseUpdateFee() {
|
|
const pythContract = this.getContract();
|
|
const result = await pythContract.methods.singleUpdateFeeInWei().call();
|
|
return { amount: result };
|
|
}
|
|
|
|
async getDataSources(): Promise<DataSource[]> {
|
|
const pythContract = this.getContract();
|
|
const result = await pythContract.methods.validDataSources().call();
|
|
return result.map(
|
|
({
|
|
chainId,
|
|
emitterAddress,
|
|
}: {
|
|
chainId: string;
|
|
emitterAddress: string;
|
|
}) => {
|
|
return {
|
|
emitterChain: Number(chainId),
|
|
emitterAddress: emitterAddress.replace("0x", ""),
|
|
};
|
|
}
|
|
);
|
|
}
|
|
|
|
async getGovernanceDataSource(): Promise<DataSource> {
|
|
const pythContract = this.getContract();
|
|
const [chainId, emitterAddress] = await pythContract.methods
|
|
.governanceDataSource()
|
|
.call();
|
|
return {
|
|
emitterChain: Number(chainId),
|
|
emitterAddress: emitterAddress.replace("0x", ""),
|
|
};
|
|
}
|
|
|
|
async executeUpdatePriceFeed(senderPrivateKey: PrivateKey, vaas: Buffer[]) {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
|
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
|
const priceFeedUpdateData = vaas.map((vaa) => "0x" + vaa.toString("hex"));
|
|
const updateFee = await pythContract.methods
|
|
.getUpdateFee(priceFeedUpdateData)
|
|
.call();
|
|
const transactionObject =
|
|
pythContract.methods.updatePriceFeeds(priceFeedUpdateData);
|
|
const result = await this.chain.estiamteAndSendTransaction(
|
|
transactionObject,
|
|
{ from: address, value: updateFee }
|
|
);
|
|
return { id: result.transactionHash, info: result };
|
|
}
|
|
|
|
async executeGovernanceInstruction(
|
|
senderPrivateKey: PrivateKey,
|
|
vaa: Buffer
|
|
) {
|
|
const web3 = new Web3(this.chain.getRpcUrl());
|
|
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
|
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
|
const transactionObject = pythContract.methods.executeGovernanceInstruction(
|
|
"0x" + vaa.toString("hex")
|
|
);
|
|
const result = await this.chain.estiamteAndSendTransaction(
|
|
transactionObject,
|
|
{ from: address }
|
|
);
|
|
return { id: result.transactionHash, info: result };
|
|
}
|
|
|
|
getChain(): EvmChain {
|
|
return this.chain;
|
|
}
|
|
|
|
toJson() {
|
|
return {
|
|
chain: this.chain.getId(),
|
|
address: this.address,
|
|
type: EvmPriceFeedContract.type,
|
|
};
|
|
}
|
|
}
|