remove pyth2wormhole

This commit is contained in:
Evan Gray 2022-03-05 04:30:29 +00:00 committed by Evan Gray
parent f44837578b
commit a3272dce4a
85 changed files with 70 additions and 23863 deletions

View File

@ -40,7 +40,6 @@ config.define_string("webHost", False, "Public hostname for port forwards")
# Components
config.define_bool("algorand", False, "Enable Algorand component")
config.define_bool("solana", False, "Enable Solana component")
config.define_bool("pyth", False, "Enable Pyth-to-Wormhole component")
config.define_bool("explorer", False, "Enable explorer component")
config.define_bool("bridge_ui", False, "Enable bridge UI component")
config.define_bool("e2e", False, "Enable E2E testing stack")
@ -57,7 +56,6 @@ webHost = cfg.get("webHost", "localhost")
algorand = cfg.get("algorand", True)
solana = cfg.get("solana", True)
ci = cfg.get("ci", False)
pyth = cfg.get("pyth", ci)
explorer = cfg.get("explorer", ci)
bridge_ui = cfg.get("bridge_ui", ci)
e2e = cfg.get("e2e", ci)
@ -279,54 +277,6 @@ docker_build(
],
)
if solana and pyth:
# pyth autopublisher
docker_build(
ref = "pyth",
context = ".",
dockerfile = "third_party/pyth/Dockerfile.pyth",
)
k8s_yaml_with_ns("./devnet/pyth.yaml")
k8s_resource(
"pyth",
resource_deps = ["solana-devnet"],
labels = ["solana"],
trigger_mode = trigger_mode,
)
# pyth2wormhole client autoattester
docker_build(
ref = "p2w-attest",
context = ".",
only = ["./solana", "./third_party"],
dockerfile = "./third_party/pyth/Dockerfile.p2w-attest",
ignore = ["./solana/*/target"],
)
# Automatic pyth2wormhole relay, showcasing p2w-sdk
docker_build(
ref = "p2w-relay",
context = ".",
dockerfile = "./third_party/pyth/p2w-relay/Dockerfile",
)
k8s_yaml_with_ns("devnet/p2w-attest.yaml")
k8s_resource(
"p2w-attest",
resource_deps = ["solana-devnet", "pyth", "guardian"],
port_forwards = [],
labels = ["solana"],
trigger_mode = trigger_mode,
)
k8s_yaml_with_ns("devnet/p2w-relay.yaml")
k8s_resource(
"p2w-relay",
resource_deps = ["solana-devnet", "eth-devnet", "pyth", "guardian", "p2w-attest", "proto-gen-web", "wasm-gen"],
port_forwards = [],
)
k8s_yaml_with_ns("devnet/eth-devnet.yaml")
k8s_resource(

View File

@ -1,52 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: p2w-attest
labels:
app: p2w-attest
spec:
ports:
- port: 4343
name: p2w-attest
protocol: TCP
clusterIP: None
selector:
app: p2w-attest
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: p2w-attest
spec:
selector:
matchLabels:
app: p2w-attest
serviceName: p2w-attest
replicas: 1
template:
metadata:
labels:
app: p2w-attest
spec:
restartPolicy: Always
terminationGracePeriodSeconds: 0
containers:
- name: p2w-attest
image: p2w-attest
command:
- python3
- /usr/src/pyth/p2w_autoattest.py
env:
- name: P2W_INITIALIZE_SOL_CONTRACT
value: "1"
tty: true
readinessProbe:
tcpSocket:
port: 2000
periodSeconds: 1
failureThreshold: 300
ports:
- containerPort: 4343
name: p2w-attest
protocol: TCP

View File

@ -1,42 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: p2w-relay
labels:
app: p2w-relay
spec:
clusterIP: None
selector:
app: p2w-relay
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: p2w-relay
spec:
selector:
matchLabels:
app: p2w-relay
serviceName: p2w-relay
replicas: 1
template:
metadata:
labels:
app: p2w-relay
spec:
restartPolicy: Always
terminationGracePeriodSeconds: 0
containers:
- name: p2w-relay
image: p2w-relay
command:
- npm
- start
workingDir: /usr/src/third_party/pyth/p2w-relay/
tty: true
readinessProbe:
tcpSocket:
port: 2000
periodSeconds: 1
failureThreshold: 300

View File

@ -1,47 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: pyth
labels:
app: pyth
spec:
clusterIP: None
selector:
app: pyth
ports:
- port: 4242
name: pyth-accounts
protocol: TCP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: pyth
spec:
selector:
matchLabels:
app: pyth
serviceName: pyth
template:
metadata:
labels:
app: pyth
spec:
restartPolicy: Always
terminationGracePeriodSeconds: 0
containers:
- name: pyth-publisher
image: pyth
command:
- python3
- /opt/pyth/pyth_publisher.py
readinessProbe:
tcpSocket:
port: 2000
periodSeconds: 1
failureThreshold: 300
ports:
- containerPort: 4242
name: pyth-accounts
protocol: TCP

View File

@ -54,12 +54,6 @@ spec:
- metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s
- /opt/solana/deps/spl_token_metadata.so
- --bpf-program
- gMYYig2utAxVoXnM9UhtTWrt8e7x2SVBZqsWZJeT5Gw # Derived from pyth_program.json
- /opt/solana/deps/pyth_oracle.so
- --bpf-program
- P2WH424242424242424242424242424242424242424
- /opt/solana/deps/pyth2wormhole.so
- --bpf-program
- Ex9bCdVMSfx7EzB3pgSi2R4UHwJAXvTw18rBQm5YQ8gK
- /opt/solana/deps/wormhole_migration.so
- --log

View File

@ -17,14 +17,12 @@
| Token Bridge | SOL | B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE | |
| NFT Bridge | SOL | NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA | |
| Migration Contract | SOL | Ex9bCdVMSfx7EzB3pgSi2R4UHwJAXvTw18rBQm5YQ8gK | |
| P2W Emitter | SOL | 8fuAZUxHecYLMC76ZNjYzwRybUiDv9LhkRQsAccEykLr | |
| Test Wallet | Terra | terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v | Mnemonic: `notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius` |
| Test CW20 | Terra | terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh | Tokens minted to Test Wallet |
| Test CW721 | Terra | terra1l425neayde0fzfcv3apkyk4zqagvflm6cmha9v | NFTs minted to Test Wallet |
| Test CW721 | Terra | terra18dt935pdcn2ka6l0syy5gt20wa48n3mktvdvjj | NFTs minted to Test Wallet |
| Bridge Core | Terra | terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 | |
| Token Bridge | Terra | terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4 | |
| NFT Bridge | Terra | terra19zpyd046u4swqpksr3n44cej4j8pg6ah2y6dcg | |
| Pyth Bridge | Terra | terra1plju286nnfj3z54wgcggd4enwaa9fgf5kgrgzl | |
| NFT Bridge | Terra | terra1plju286nnfj3z54wgcggd4enwaa9fgf5kgrgzl | |
| Governance Emitter | Universal | 0x0000000000000000000000000000000000000000000000000000000000000004 / 11111111111111111111111111111115 | Emitter Chain: 0x01 |
### Terra

View File

@ -16,10 +16,3 @@ BRIDGE_INIT_CHAIN_ID= # 0x02
BRIDGE_INIT_GOV_CHAIN_ID= # 0x3
BRIDGE_INIT_GOV_CONTRACT= # 0x0000000000000000000000000000000000000000000000000000000000000004
BRIDGE_INIT_WETH= # 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
# Pyth Migrations # Example Format
PYTH_INIT_CHAIN_ID= # 0x2
PYTH_INIT_GOV_CHAIN_ID= # 0x3
PYTH_INIT_GOV_CONTRACT= # 0x0000000000000000000000000000000000000000000000000000000000000004
PYTH_TO_WORMHOLE_CHAIN_ID= # 0x1
PYTH_TO_WORMHOLE_EMITTER= # 8fuAZUxHecYLMC76ZNjYzwRybUiDv9LhkRQsAccEykLr

View File

@ -9,10 +9,3 @@ BRIDGE_INIT_CHAIN_ID=0x2
BRIDGE_INIT_GOV_CHAIN_ID=0x1
BRIDGE_INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
BRIDGE_INIT_WETH=0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E
#Pyth Migrations
PYTH_INIT_CHAIN_ID=0x2
PYTH_INIT_GOV_CHAIN_ID=0x3
PYTH_INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
PYTH_TO_WORMHOLE_CHAIN_ID=0x1
PYTH_TO_WORMHOLE_EMITTER=8fuAZUxHecYLMC76ZNjYzwRybUiDv9LhkRQsAccEykLr

View File

@ -1,98 +0,0 @@
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "../libraries/external/BytesLib.sol";
import "./PythGetters.sol";
import "./PythSetters.sol";
import "./PythStructs.sol";
import "./PythGovernance.sol";
contract Pyth is PythGovernance {
using BytesLib for bytes;
function attestPrice(bytes memory encodedVm) public returns (PythStructs.PriceAttestation memory pa) {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
require(valid, reason);
require(verifyPythVM(vm), "invalid emitter");
PythStructs.PriceAttestation memory price = parsePriceAttestation(vm.payload);
PythStructs.PriceAttestation memory latestPrice = latestAttestation(price.productId, price.priceType);
if(price.timestamp > latestPrice.timestamp) {
setLatestAttestation(price.productId, price.priceType, price);
}
return price;
}
function verifyPythVM(IWormhole.VM memory vm) public view returns (bool valid) {
if (vm.emitterChainId != pyth2WormholeChainId()) {
return false;
}
if (vm.emitterAddress != pyth2WormholeEmitter()) {
return false;
}
return true;
}
function parsePriceAttestation(bytes memory encodedPriceAttestation) public pure returns (PythStructs.PriceAttestation memory pa) {
uint index = 0;
pa.magic = encodedPriceAttestation.toUint32(index);
index += 4;
require(pa.magic == 0x50325748, "invalid protocol");
pa.version = encodedPriceAttestation.toUint16(index);
index += 2;
require(pa.version == 1, "invalid protocol");
pa.payloadId = encodedPriceAttestation.toUint8(index);
index += 1;
require(pa.payloadId == 1, "invalid PriceAttestation");
pa.productId = encodedPriceAttestation.toBytes32(index);
index += 32;
pa.priceId = encodedPriceAttestation.toBytes32(index);
index += 32;
pa.priceType = encodedPriceAttestation.toUint8(index);
index += 1;
pa.price = int64(encodedPriceAttestation.toUint64(index));
index += 8;
pa.exponent = int32(encodedPriceAttestation.toUint32(index));
index += 4;
pa.twap.value = int64(encodedPriceAttestation.toUint64(index));
index += 8;
pa.twap.numerator = int64(encodedPriceAttestation.toUint64(index));
index += 8;
pa.twap.denominator = int64(encodedPriceAttestation.toUint64(index));
index += 8;
pa.twac.value = int64(encodedPriceAttestation.toUint64(index));
index += 8;
pa.twac.numerator = int64(encodedPriceAttestation.toUint64(index));
index += 8;
pa.twac.denominator = int64(encodedPriceAttestation.toUint64(index));
index += 8;
pa.confidenceInterval = encodedPriceAttestation.toUint64(index);
index += 8;
pa.status = encodedPriceAttestation.toUint8(index);
index += 1;
pa.corpAct = encodedPriceAttestation.toUint8(index);
index += 1;
pa.timestamp = encodedPriceAttestation.toUint64(index);
index += 8;
require(encodedPriceAttestation.length == index, "invalid PriceAttestation");
}
}

View File

@ -1,15 +0,0 @@
// contracts/Wormhole.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract PythDataBridge is ERC1967Proxy {
constructor (address implementation, bytes memory initData)
ERC1967Proxy(
implementation,
initData
)
{}
}

View File

@ -1,46 +0,0 @@
// contracts/Getters.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "../interfaces/IWormhole.sol";
import "./PythState.sol";
contract PythGetters is PythState {
function governanceActionIsConsumed(bytes32 hash) public view returns (bool) {
return _state.consumedGovernanceActions[hash];
}
function isInitialized(address impl) public view returns (bool) {
return _state.initializedImplementations[impl];
}
function wormhole() public view returns (IWormhole) {
return IWormhole(_state.wormhole);
}
function chainId() public view returns (uint16){
return _state.provider.chainId;
}
function governanceChainId() public view returns (uint16){
return _state.provider.governanceChainId;
}
function governanceContract() public view returns (bytes32){
return _state.provider.governanceContract;
}
function pyth2WormholeChainId() public view returns (uint16){
return _state.provider.pyth2WormholeChainId;
}
function pyth2WormholeEmitter() public view returns (bytes32){
return _state.provider.pyth2WormholeEmitter;
}
function latestAttestation(bytes32 product, uint8 priceType) public view returns (PythStructs.PriceAttestation memory attestation){
return _state.latestAttestations[product][priceType];
}
}

View File

@ -1,89 +0,0 @@
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
import "../libraries/external/BytesLib.sol";
import "./PythGetters.sol";
import "./PythSetters.sol";
import "./PythStructs.sol";
import "../interfaces/IWormhole.sol";
contract PythGovernance is PythGetters, PythSetters, ERC1967Upgrade {
using BytesLib for bytes;
bytes32 constant module = 0x0000000000000000000000000000000000000000000000000000000050797468;
// Execute a UpgradeContract governance message
function upgrade(bytes memory encodedVM) public {
(IWormhole.VM memory vm, bool valid, string memory reason) = verifyGovernanceVM(encodedVM);
require(valid, reason);
setGovernanceActionConsumed(vm.hash);
PythStructs.UpgradeContract memory implementation = parseContractUpgrade(vm.payload);
require(implementation.module == module, "wrong module");
require(implementation.chain == chainId(), "wrong chain id");
upgradeImplementation(implementation.newContract);
}
function verifyGovernanceVM(bytes memory encodedVM) internal view returns (IWormhole.VM memory parsedVM, bool isValid, string memory invalidReason){
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVM);
if(!valid){
return (vm, valid, reason);
}
if (vm.emitterChainId != governanceChainId()) {
return (vm, false, "wrong governance chain");
}
if (vm.emitterAddress != governanceContract()) {
return (vm, false, "wrong governance contract");
}
if(governanceActionIsConsumed(vm.hash)){
return (vm, false, "governance action already consumed");
}
return (vm, true, "");
}
event ContractUpgraded(address indexed oldContract, address indexed newContract);
function upgradeImplementation(address newImplementation) internal {
address currentImplementation = _getImplementation();
_upgradeTo(newImplementation);
// Call initialize function of the new implementation
(bool success, bytes memory reason) = newImplementation.delegatecall(abi.encodeWithSignature("initialize()"));
require(success, string(reason));
emit ContractUpgraded(currentImplementation, newImplementation);
}
function parseContractUpgrade(bytes memory encodedUpgrade) public pure returns (PythStructs.UpgradeContract memory cu) {
uint index = 0;
cu.module = encodedUpgrade.toBytes32(index);
index += 32;
cu.action = encodedUpgrade.toUint8(index);
index += 1;
require(cu.action == 1, "invalid ContractUpgrade 1");
cu.chain = encodedUpgrade.toUint16(index);
index += 2;
cu.newContract = address(uint160(uint256(encodedUpgrade.toBytes32(index))));
index += 32;
require(encodedUpgrade.length == index, "invalid ContractUpgrade 2");
}
}

View File

@ -1,25 +0,0 @@
// contracts/Implementation.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
import "./Pyth.sol";
contract PythImplementation is Pyth {
modifier initializer() {
address impl = ERC1967Upgrade._getImplementation();
require(
!isInitialized(impl),
"already initialized"
);
setInitialized(impl);
_;
}
}

View File

@ -1,44 +0,0 @@
// contracts/Setters.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "./PythState.sol";
contract PythSetters is PythState {
function setInitialized(address implementatiom) internal {
_state.initializedImplementations[implementatiom] = true;
}
function setGovernanceActionConsumed(bytes32 hash) internal {
_state.consumedGovernanceActions[hash] = true;
}
function setChainId(uint16 chainId) internal {
_state.provider.chainId = chainId;
}
function setGovernanceChainId(uint16 chainId) internal {
_state.provider.governanceChainId = chainId;
}
function setGovernanceContract(bytes32 governanceContract) internal {
_state.provider.governanceContract = governanceContract;
}
function setPyth2WormholeChainId(uint16 chainId) internal {
_state.provider.pyth2WormholeChainId = chainId;
}
function setPyth2WormholeEmitter(bytes32 emitterAddr) internal {
_state.provider.pyth2WormholeEmitter = emitterAddr;
}
function setWormhole(address wh) internal {
_state.wormhole = payable(wh);
}
function setLatestAttestation(bytes32 product, uint8 priceType, PythStructs.PriceAttestation memory attestation) internal {
_state.latestAttestations[product][priceType] = attestation;
}
}

View File

@ -1,36 +0,0 @@
// contracts/PythSetup.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "./PythSetters.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
contract PythSetup is PythSetters, ERC1967Upgrade {
function setup(
address implementation,
uint16 chainId,
address wormhole,
uint16 governanceChainId,
bytes32 governanceContract,
uint16 pyth2WormholeChainId,
bytes32 pyth2WormholeEmitter
) public {
setChainId(chainId);
setWormhole(wormhole);
setGovernanceChainId(governanceChainId);
setGovernanceContract(governanceContract);
setPyth2WormholeChainId(pyth2WormholeChainId);
setPyth2WormholeEmitter(pyth2WormholeEmitter);
_upgradeTo(implementation);
}
}

View File

@ -1,38 +0,0 @@
// contracts/State.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "./PythStructs.sol";
contract PythStorage {
struct Provider {
uint16 chainId;
uint16 governanceChainId;
bytes32 governanceContract;
uint16 pyth2WormholeChainId;
bytes32 pyth2WormholeEmitter;
}
struct State {
address payable wormhole;
Provider provider;
// Mapping of consumed governance actions
mapping(bytes32 => bool) consumedGovernanceActions;
// Mapping of initialized implementations
mapping(address => bool) initializedImplementations;
// Mapping of cached price attestations
// productId => priceType => PriceAttestation
mapping(bytes32 => mapping(uint8 => PythStructs.PriceAttestation)) latestAttestations;
}
}
contract PythState {
PythStorage.State _state;
}

View File

@ -1,50 +0,0 @@
// contracts/Structs.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "../libraries/external/BytesLib.sol";
contract PythStructs {
using BytesLib for bytes;
struct Ema {
int64 value;
int64 numerator;
int64 denominator;
}
struct PriceAttestation {
uint32 magic; // constant "P2WH"
uint16 version;
// PayloadID uint8 = 1
uint8 payloadId;
bytes32 productId;
bytes32 priceId;
uint8 priceType;
int64 price;
int32 exponent;
Ema twap;
Ema twac;
uint64 confidenceInterval;
uint8 status;
uint8 corpAct;
uint64 timestamp;
}
struct UpgradeContract {
bytes32 module;
uint8 action;
uint16 chain;
address newContract;
}
}

View File

@ -1,16 +0,0 @@
// contracts/Implementation.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "../PythImplementation.sol";
contract MockPythImplementation is PythImplementation {
function initialize() initializer public {
// this function needs to be exposed for an upgrade to pass
}
function testNewImplementationActive() external pure returns (bool) {
return true;
}
}

View File

@ -1,40 +0,0 @@
require('dotenv').config({ path: "../.env" });
const bs58 = require("bs58");
const PythDataBridge = artifacts.require("PythDataBridge");
const PythImplementation = artifacts.require("PythImplementation");
const PythSetup = artifacts.require("PythSetup");
const Wormhole = artifacts.require("Wormhole");
const chainId = process.env.PYTH_INIT_CHAIN_ID;
const governanceChainId = process.env.PYTH_INIT_GOV_CHAIN_ID;
const governanceContract = process.env.PYTH_INIT_GOV_CONTRACT; // bytes32
const pyth2WormholeChainId = process.env.PYTH_TO_WORMHOLE_CHAIN_ID;
const pyth2WormholeEmitter = bs58.decode(process.env.PYTH_TO_WORMHOLE_EMITTER); // base58, must fit into bytes32
console.log("Deploying Pyth with emitter", pyth2WormholeEmitter.toString("hex"))
module.exports = async function (deployer) {
// deploy implementation
await deployer.deploy(PythImplementation);
// deploy implementation
await deployer.deploy(PythSetup);
// encode initialisation data
const setup = new web3.eth.Contract(PythSetup.abi, PythSetup.address);
const initData = setup.methods.setup(
PythImplementation.address,
chainId,
(await Wormhole.deployed()).address,
governanceChainId,
governanceContract,
pyth2WormholeChainId,
"0x" + pyth2WormholeEmitter.toString("hex"),
).encodeABI();
// deploy proxy
await deployer.deploy(PythDataBridge, PythSetup.address, initData);
};

View File

@ -20,7 +20,7 @@
"scripts": {
"build": "truffle compile",
"test": "mkdir -p build/contracts && cp node_modules/@openzeppelin/contracts/build/contracts/* build/contracts/ && truffle test",
"migrate": "mkdir -p build/contracts && cp node_modules/@openzeppelin/contracts/build/contracts/* build/contracts/ && truffle migrate --to 5",
"migrate": "mkdir -p build/contracts && cp node_modules/@openzeppelin/contracts/build/contracts/* build/contracts/ && truffle migrate --to 4",
"flatten": "mkdir -p node_modules/@poanet/solidity-flattener/contracts && cp -r contracts/* node_modules/@poanet/solidity-flattener/contracts/ && poa-solidity-flattener",
"deploy-bridge-implementation-only": "mkdir -p build/contracts && cp node_modules/@openzeppelin/contracts/build/contracts/* build/contracts/ && truffle migrate --f 6 --to 6",
"deploy_weth9": "mkdir -p build/contracts && cp node_modules/@openzeppelin/contracts/build/contracts/* build/contracts/ && truffle migrate --f 9",

View File

@ -42,7 +42,7 @@ module.exports = async function(callback) {
//Contracts deployed via this script deploy to an address which is determined by the number of transactions
//which have been performed on the chain.
//This is, however, variable. For example, if you optionally deploy the pyth contracts, more transactions are
//This is, however, variable. For example, if you optionally deploy contracts, more transactions are
//performed than if you didn't.
//In order to make sure the test contracts deploy to a location
//which is deterministic with regard to other environment conditions, we fire bogus transactions up to a safe

View File

@ -20,7 +20,7 @@ module.exports = async function (callback) {
});
// Register the terra NFT bridge endpoint
await nftBridge.methods.registerChain("0x010000000001008ebe6a1971ae336bb7817aa7e8ffc13e1582ebbc00dc85e33b592dfea998787a1a9ccece2efce7fcdf228153baafb6f1232b320805c82fac90b5e49c3b5ad4fd0100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000004e46544272696467650100000003000000000000000000000000288246bebae560e006d01c675ae332ac8e146bb7").send({
await nftBridge.methods.registerChain("0x01000000000100880ab34efd145fa1c9c4f2f7b233f3bc99cb730ab2d2138babf989a2555e2e8f737b759f91970d300c4364249b0740b134dbefb943fb55fd76483261ce68c17b0100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000023c2cdd0000000000000000000000000000000000000000000000004e465442726964676501000000030000000000000000000000000fe5c51f539a651152ae461086d733777a54a134").send({
value: 0,
from: accounts[0],
gasLimit: 2000000

View File

@ -1,240 +0,0 @@
const jsonfile = require('jsonfile');
const elliptic = require('elliptic');
const BigNumber = require('bignumber.js');
const Wormhole = artifacts.require("Wormhole");
const PythDataBridge = artifacts.require("PythDataBridge");
const PythImplementation = artifacts.require("PythImplementation");
const MockPythImplementation = artifacts.require("MockPythImplementation");
const testSigner1PK = "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
const testSigner2PK = "892330666a850761e7370376430bb8c2aa1494072d3bfeaed0c4fa3d5a9135fe";
const WormholeImplementationFullABI = jsonfile.readFileSync("build/contracts/Implementation.json").abi
const P2WImplementationFullABI = jsonfile.readFileSync("build/contracts/PythImplementation.json").abi
contract("Pyth", function () {
const testSigner1 = web3.eth.accounts.privateKeyToAccount(testSigner1PK);
const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
const testChainId = "2";
const testGovernanceChainId = "3";
const testGovernanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004";
const testPyth2WormholeChainId = "5";
const testPyth2WormholeEmitter = "0x0000000000000000000000000000000000000000000000000000000000000006";
it("should be initialized with the correct signers and values", async function(){
const initialized = new web3.eth.Contract(P2WImplementationFullABI, PythDataBridge.address);
// chain id
const chainId = await initialized.methods.chainId().call();
assert.equal(chainId, testChainId);
// governance
const governanceChainId = await initialized.methods.governanceChainId().call();
assert.equal(governanceChainId, testGovernanceChainId);
const governanceContract = await initialized.methods.governanceContract().call();
assert.equal(governanceContract, testGovernanceContract);
// pyth2wormhole
const pyth2wormChain = await initialized.methods.pyth2WormholeChainId().call();
assert.equal(pyth2wormChain, testPyth2WormholeChainId);
const pyth2wormEmitter = await initialized.methods.pyth2WormholeEmitter().call();
assert.equal(pyth2wormEmitter, testPyth2WormholeEmitter);
})
it("should accept a valid upgrade", async function() {
const initialized = new web3.eth.Contract(P2WImplementationFullABI, PythDataBridge.address);
const accounts = await web3.eth.getAccounts();
const mock = await MockPythImplementation.new();
let data = [
"0x0000000000000000000000000000000000000000000000000000000050797468",
"01",
web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
web3.eth.abi.encodeParameter("address", mock.address).substring(2),
].join('')
const vm = await signAndEncodeVM(
1,
1,
testGovernanceChainId,
testGovernanceContract,
0,
data,
[
testSigner1PK
],
0,
0
);
let before = await web3.eth.getStorageAt(PythDataBridge.address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc");
assert.equal(before.toLowerCase(), PythImplementation.address.toLowerCase());
await initialized.methods.upgrade("0x" + vm).send({
value : 0,
from : accounts[0],
gasLimit : 2000000
});
let after = await web3.eth.getStorageAt(PythDataBridge.address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc");
assert.equal(after.toLowerCase(), mock.address.toLowerCase());
const mockImpl = new web3.eth.Contract(MockPythImplementation.abi, PythDataBridge.address);
let isUpgraded = await mockImpl.methods.testNewImplementationActive().call();
assert.ok(isUpgraded);
})
let testUpdate = "0x"+
"503257480001011515151515151515151515151515151515151515151515151515151515151515DEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDEDE01DEADBEEFDEADBABEFFFFFFFDFFFFFFFFFFFFFFD6000000000000000F0000000000000025000000000000002A000000000000045700000000000008AE0000000000000065010000000000075BCD15";
it("should parse price update correctly", async function() {
const initialized = new web3.eth.Contract(P2WImplementationFullABI, PythDataBridge.address);
let parsed = await initialized.methods.parsePriceAttestation(testUpdate).call();
assert.equal(parsed.magic, 1345476424);
assert.equal(parsed.version, 1);
assert.equal(parsed.payloadId, 1);
assert.equal(parsed.productId, "0x1515151515151515151515151515151515151515151515151515151515151515");
assert.equal(parsed.priceId, "0xdededededededededededededededededededededededededededededededede");
assert.equal(parsed.priceType, 1);
assert.equal(parsed.price, -2401053088876217666);
assert.equal(parsed.exponent, -3);
assert.equal(parsed.twap.value, -42);
assert.equal(parsed.twap.numerator, 15);
assert.equal(parsed.twap.denominator, 37);
assert.equal(parsed.twac.value, 42);
assert.equal(parsed.twac.numerator, 1111);
assert.equal(parsed.twac.denominator, 2222);
assert.equal(parsed.confidenceInterval, 101);
assert.equal(parsed.status, 1);
assert.equal(parsed.corpAct, 0);
assert.equal(parsed.timestamp, 123456789);
})
it("should attest price updates over wormhole", async function() {
const initialized = new web3.eth.Contract(P2WImplementationFullABI, PythDataBridge.address);
const accounts = await web3.eth.getAccounts();
const vm = await signAndEncodeVM(
1,
1,
testPyth2WormholeChainId,
testPyth2WormholeEmitter,
0,
testUpdate,
[
testSigner1PK
],
0,
0
);
let result = await initialized.methods.attestPrice("0x"+vm).send({
value : 0,
from : accounts[0],
gasLimit : 2000000
});
})
it("should cache price updates", async function() {
const initialized = new web3.eth.Contract(P2WImplementationFullABI, PythDataBridge.address);
let cached = await initialized.methods.latestAttestation("0x1515151515151515151515151515151515151515151515151515151515151515", 1).call();
assert.equal(cached.magic, 1345476424);
assert.equal(cached.version, 1);
assert.equal(cached.payloadId, 1);
assert.equal(cached.productId, "0x1515151515151515151515151515151515151515151515151515151515151515");
assert.equal(cached.priceId, "0xdededededededededededededededededededededededededededededededede");
assert.equal(cached.priceType, 1);
assert.equal(cached.price, -2401053088876217666);
assert.equal(cached.exponent, -3);
assert.equal(cached.twap.value, -42);
assert.equal(cached.twap.numerator, 15);
assert.equal(cached.twap.denominator, 37);
assert.equal(cached.twac.value, 42);
assert.equal(cached.twac.numerator, 1111);
assert.equal(cached.twac.denominator, 2222);
assert.equal(cached.confidenceInterval, 101);
assert.equal(cached.status, 1);
assert.equal(cached.corpAct, 0);
assert.equal(cached.timestamp, 123456789);
})
});
const signAndEncodeVM = async function (
timestamp,
nonce,
emitterChainId,
emitterAddress,
sequence,
data,
signers,
guardianSetIndex,
consistencyLevel
) {
const body = [
web3.eth.abi.encodeParameter("uint32", timestamp).substring(2 + (64 - 8)),
web3.eth.abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
web3.eth.abi.encodeParameter("uint16", emitterChainId).substring(2 + (64 - 4)),
web3.eth.abi.encodeParameter("bytes32", emitterAddress).substring(2),
web3.eth.abi.encodeParameter("uint64", sequence).substring(2 + (64 - 16)),
web3.eth.abi.encodeParameter("uint8", consistencyLevel).substring(2 + (64 - 2)),
data.substr(2)
]
const hash = web3.utils.soliditySha3(web3.utils.soliditySha3("0x" + body.join("")))
let signatures = "";
for (let i in signers) {
const ec = new elliptic.ec("secp256k1");
const key = ec.keyFromPrivate(signers[i]);
const signature = key.sign(hash.substr(2), {canonical: true});
const packSig = [
web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
zeroPadBytes(signature.r.toString(16), 32),
zeroPadBytes(signature.s.toString(16), 32),
web3.eth.abi.encodeParameter("uint8", signature.recoveryParam).substr(2 + (64 - 2)),
]
signatures += packSig.join("")
}
const vm = [
web3.eth.abi.encodeParameter("uint8", 1).substring(2 + (64 - 2)),
web3.eth.abi.encodeParameter("uint32", guardianSetIndex).substring(2 + (64 - 8)),
web3.eth.abi.encodeParameter("uint8", signers.length).substring(2 + (64 - 2)),
signatures,
body.join("")
].join("");
return vm
}
function zeroPadBytes(value, length) {
while (value.length < 2 * length) {
value = "0" + value;
}
return value;
}

View File

@ -30,7 +30,7 @@ var NFTEmitters = map[string]string{
// devnet
"96ee982293251b48729804c8e8b24b553eb6b887867024948d2236fd37a577ab": "NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA", // solana
"00000000000000000000000026b4afb60d6c903165150c6f0aa14f8016be4aec": "0x26b4afb60d6c903165150c6f0aa14f8016be4aec", // ethereum
"000000000000000000000000288246bebae560e006d01c675ae332ac8e146bb7": "terra19zpyd046u4swqpksr3n44cej4j8pg6ah2y6dcg",// terra
"0000000000000000000000000fe5c51f539a651152ae461086d733777a54a134": "terra1plju286nnfj3z54wgcggd4enwaa9fgf5kgrgzl",// terra
}
var TokenTransferEmitters = map[string]string{
// mainnet

View File

@ -34,12 +34,12 @@ export const TERRA_GAS_PRICES_URL = ci
export const TERRA_CORE_BRIDGE_ADDRESS =
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
export const TERRA_NFT_BRIDGE_ADDRESS =
"terra19zpyd046u4swqpksr3n44cej4j8pg6ah2y6dcg";
"terra1plju286nnfj3z54wgcggd4enwaa9fgf5kgrgzl";
export const TERRA_PRIVATE_KEY =
"quality vacuum heart guard buzz spike sight swarm shove special gym robust assume sudden deposit grid alcohol choice devote leader tilt noodle tide penalty";
export const TEST_ERC721 = "0x5b9b42d6e4B2e4Bf8d42Eba32D46918e10899B66";
export const TERRA_CW721_CODE_ID = 8;
export const TEST_CW721 = "terra1l425neayde0fzfcv3apkyk4zqagvflm6cmha9v";
export const TERRA_CW721_CODE_ID = 7;
export const TEST_CW721 = "terra18dt935pdcn2ka6l0syy5gt20wa48n3mktvdvjj";
export const TEST_SOLANA_TOKEN = "BVxyYhm498L79r4HMQ9sxZ5bi41DmJmeWZ7SCS7Cyvna";
export const WORMHOLE_RPC_HOSTS = ci
? ["http://guardian:7071"]

View File

@ -28,17 +28,6 @@ RUN cargo init --lib /tmp/decoy-crate && \
cd /tmp/decoy-crate && cargo build-bpf && \
rm -rf /tmp/decoy-crate
# Cache Pyth sources
# This comes soon after mainnet-v2.1
ENV PYTH_SRC_REV=31e3188bbf52ec1a25f71e4ab969378b27415b0a
ENV PYTH_DIR=/usr/src/pyth/pyth-client
WORKDIR $PYTH_DIR
ADD https://github.com/pyth-network/pyth-client/archive/$PYTH_SRC_REV.tar.gz .
# GitHub appends revision to dir in archive
RUN tar -xvf *.tar.gz && rm -rf *.tar.gz && mv pyth-client-$PYTH_SRC_REV pyth-client
# Add bridge contract sources
WORKDIR /usr/src/bridge
@ -52,12 +41,10 @@ ENV BRIDGE_ADDRESS="Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
RUN --mount=type=cache,target=bridge/target \
--mount=type=cache,target=modules/token_bridge/target \
--mount=type=cache,target=modules/nft_bridge/target \
--mount=type=cache,target=pyth2wormhole/target \
--mount=type=cache,target=migration/target \
cargo build-bpf --manifest-path "bridge/program/Cargo.toml" -- --locked && \
cargo build-bpf --manifest-path "bridge/cpi_poster/Cargo.toml" -- --locked && \
cargo build-bpf --manifest-path "modules/token_bridge/program/Cargo.toml" -- --locked && \
cargo build-bpf --manifest-path "pyth2wormhole/program/Cargo.toml" -- --locked && \
cargo build-bpf --manifest-path "modules/nft_bridge/program/Cargo.toml" -- --locked && \
cargo build-bpf --manifest-path "migration/Cargo.toml" -- --locked && \
cp bridge/target/deploy/bridge.so /opt/solana/deps/bridge.so && \
@ -65,13 +52,7 @@ RUN --mount=type=cache,target=bridge/target \
cp migration/target/deploy/wormhole_migration.so /opt/solana/deps/wormhole_migration.so && \
cp modules/token_bridge/target/deploy/token_bridge.so /opt/solana/deps/token_bridge.so && \
cp modules/nft_bridge/target/deploy/nft_bridge.so /opt/solana/deps/nft_bridge.so && \
cp modules/token_bridge/token-metadata/spl_token_metadata.so /opt/solana/deps/spl_token_metadata.so && \
cp pyth2wormhole/target/deploy/pyth2wormhole.so /opt/solana/deps/pyth2wormhole.so
# Build the Pyth Solana program
WORKDIR $PYTH_DIR/pyth-client/program
RUN make SOLANA=~/.local/share/solana/install/active_release/bin OUT_DIR=../target && \
cp ../target/oracle.so /opt/solana/deps/pyth_oracle.so
cp modules/token_bridge/token-metadata/spl_token_metadata.so /opt/solana/deps/spl_token_metadata.so
ENV RUST_LOG="solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=trace,solana_bpf_loader=debug,solana_rbpf=debug"
ENV RUST_BACKTRACE=1

View File

@ -22,7 +22,6 @@ COPY bridge bridge
COPY modules modules
COPY solitaire solitaire
COPY migration migration
COPY pyth2wormhole pyth2wormhole
# wasm-bindgen 0.2.74 generates JavaScript bindings for SystemInstruction exported from solana-program 1.9.4.
# The generated JavaScript references a non-existent function (wasm.__wbg_systeminstruction_free) that leads
@ -72,17 +71,6 @@ RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=modules/nft_bridge/target \
cd modules/nft_bridge/program && /usr/local/cargo/bin/wasm-pack build --target nodejs -d nodejs -- --features wasm --locked
# Compile pyth2wormhole
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=pyth2wormhole/target \
cd pyth2wormhole/program \
&& /usr/local/cargo/bin/wasm-pack build --target bundler -d bundler -- --features wasm --locked
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=pyth2wormhole/target \
cd pyth2wormhole/program \
&& /usr/local/cargo/bin/wasm-pack build --target nodejs -d nodejs -- --features wasm --locked
FROM scratch AS export
COPY --from=build /usr/src/bridge/bridge/program/bundler sdk/js/src/solana/core
@ -90,9 +78,6 @@ COPY --from=build /usr/src/bridge/modules/token_bridge/program/bundler sdk/js/sr
COPY --from=build /usr/src/bridge/migration/bundler sdk/js/src/solana/migration
COPY --from=build /usr/src/bridge/modules/nft_bridge/program/bundler sdk/js/src/solana/nft
COPY --from=build /usr/src/bridge/pyth2wormhole/program/bundler third_party/pyth/p2w-sdk/src/solana/p2w-core
COPY --from=build /usr/src/bridge/bridge/program/bundler third_party/pyth/p2w-sdk/src/solana/wormhole-core
COPY --from=build /usr/src/bridge/bridge/program/nodejs sdk/js/src/solana/core-node
COPY --from=build /usr/src/bridge/modules/token_bridge/program/nodejs sdk/js/src/solana/token-node
COPY --from=build /usr/src/bridge/migration/nodejs sdk/js/src/solana/migration-node

View File

@ -100,7 +100,7 @@ popd
pushd /usr/src/clients/nft_bridge
# Register the NFT Bridge Endpoint on ETH
node main.js solana execute_governance_vaa $(node main.js generate_register_chain_vaa 2 0x00000000000000000000000026b4afb60d6c903165150c6f0aa14f8016be4aec)
node main.js solana execute_governance_vaa $(node main.js generate_register_chain_vaa 3 0x000000000000000000000000288246bebae560e006d01c675ae332ac8e146bb7)
node main.js solana execute_governance_vaa $(node main.js generate_register_chain_vaa 3 0x0000000000000000000000000fe5c51f539a651152ae461086d733777a54a134)
popd
# Let k8s startup probe succeed

View File

@ -1 +0,0 @@
[39,20,181,104,82,27,70,145,227,136,168,14,170,24,33,88,145,152,180,229,219,142,247,114,237,79,52,97,84,65,213,172,49,165,99,116,254,135,110,132,214,114,59,200,109,253,45,43,74,172,107,84,162,223,23,15,78,167,240,137,234,123,4,231]

View File

@ -1 +0,0 @@
[151,156,152,229,131,186,5,254,107,42,234,87,191,209,182,237,170,57,174,150,37,14,5,58,100,237,114,141,46,22,155,104,10,20,225,112,227,95,250,0,102,170,119,34,187,74,144,163,181,123,233,253,191,6,2,70,127,227,138,51,98,209,205,172]

View File

@ -1 +0,0 @@
[62,189,176,181,215,49,125,17,130,43,109,83,115,112,151,110,117,239,235,54,205,209,6,255,76,27,210,115,206,166,217,165,250,48,211,191,77,246,195,18,170,246,162,103,141,129,14,143,127,4,243,114,79,112,11,46,90,174,215,2,63,42,134,56]

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
[workspace]
members = ["client", "program"]
[patch.crates-io]
memmap2 = { path = "../bridge/memmap2-rs" }

View File

@ -1,26 +0,0 @@
[package]
name = "pyth2wormhole-client"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["pyth2wormhole/client", "wormhole-bridge-solana/client"]
[dependencies]
borsh = "=0.9.1"
clap = "3.0.0-beta.2" # This beta assimilates structopt into clap
env_logger = "0.8.4"
log = "0.4.14"
wormhole-bridge-solana = {path = "../../bridge/program"}
pyth2wormhole = {path = "../program"}
serde = "1"
serde_yaml = "0.8"
shellexpand = "2.1.0"
solana-client = "=1.9.4"
solana-program = "=1.9.4"
solana-sdk = "=1.9.4"
solana-transaction-status = "=1.9.4"
solitaire-client = {path = "../../solitaire/client"}
solitaire = {path = "../../solitaire/program"}

View File

@ -1,84 +0,0 @@
use std::str::FromStr;
use serde::{
de::Error,
Deserialize,
Deserializer,
Serialize,
Serializer,
};
use solana_program::pubkey::Pubkey;
use solitaire::ErrBox;
/// Pyth2wormhole config specific to attestation requests
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct AttestationConfig {
pub symbols: Vec<P2WSymbol>,
}
/// Config entry for a Pyth product + price pair
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct P2WSymbol {
/// User-defined human-readable name
pub name: Option<String>,
#[serde(
deserialize_with = "pubkey_string_de",
serialize_with = "pubkey_string_ser"
)]
pub product_addr: Pubkey,
#[serde(
deserialize_with = "pubkey_string_de",
serialize_with = "pubkey_string_ser"
)]
pub price_addr: Pubkey,
}
// Helper methods for strinigified SOL addresses
fn pubkey_string_ser<S>(k: &Pubkey, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ser.serialize_str(&k.to_string())
}
fn pubkey_string_de<'de, D>(de: D) -> Result<Pubkey, D::Error>
where
D: Deserializer<'de>,
{
let pubkey_string = String::deserialize(de)?;
let pubkey = Pubkey::from_str(&pubkey_string).map_err(D::Error::custom)?;
Ok(pubkey)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sanity() -> Result<(), ErrBox> {
let initial = AttestationConfig {
symbols: vec![
P2WSymbol {
name: Some("ETH/USD".to_owned()),
product_addr: Default::default(),
price_addr: Default::default(),
},
P2WSymbol {
name: None,
product_addr: Pubkey::new(&[42u8; 32]),
price_addr: Default::default(),
},
],
};
let serialized = serde_yaml::to_string(&initial)?;
eprintln!("Serialized:\n{}", serialized);
let deserialized: AttestationConfig = serde_yaml::from_str(&serialized)?;
assert_eq!(initial, deserialized);
Ok(())
}
}

View File

@ -1,68 +0,0 @@
//! CLI options
use solana_program::pubkey::Pubkey;
use std::path::PathBuf;
use clap::Clap;
#[derive(Clap)]
#[clap(
about = "A client for the pyth2wormhole Solana program",
author = "The Wormhole Project"
)]
pub struct Cli {
#[clap(
short,
long,
default_value = "3",
about = "Logging level, where 0..=1 RUST_LOG=error and 5.. is RUST_LOG=trace"
)]
pub log_level: u32,
#[clap(
long,
about = "Identity JSON file for the entity meant to cover transaction costs",
default_value = "~/.config/solana/id.json"
)]
pub payer: String,
#[clap(short, long, default_value = "http://localhost:8899")]
pub rpc_url: String,
#[clap(long)]
pub p2w_addr: Pubkey,
#[clap(subcommand)]
pub action: Action,
}
#[derive(Clap)]
pub enum Action {
#[clap(about = "Initialize a pyth2wormhole program freshly deployed under <p2w_addr>")]
Init {
/// The bridge program account
#[clap(short = 'w', long = "wh-prog")]
wh_prog: Pubkey,
#[clap(short = 'o', long = "owner")]
owner_addr: Pubkey,
#[clap(short = 'p', long = "pyth-owner")]
pyth_owner_addr: Pubkey,
},
#[clap(
about = "Use an existing pyth2wormhole program to attest product price information to another chain"
)]
Attest {
#[clap(short = 'f', long = "--config", about = "Attestation YAML config")]
attestation_cfg: PathBuf,
},
#[clap(
about = "Update an existing pyth2wormhole program's settings (currently set owner only)"
)]
SetConfig {
/// Current owner keypair path
#[clap(long = "owner", default_value = "~/.config/solana/id.json")]
owner: String,
/// New owner to set
#[clap(long = "new-owner")]
new_owner_addr: Pubkey,
#[clap(long = "new-wh-prog")]
new_wh_prog: Pubkey,
#[clap(long = "new-pyth-owner")]
new_pyth_owner_addr: Pubkey,
},
}

View File

@ -1,38 +0,0 @@
#[derive(Deserialize, Serialize)]
pub struct Config {
symbols: Vec<P2WSymbol>,
}
/// Config entry for a Pyth2Wormhole product + price pair
#[derive(Deserialize, Serialize)]
pub struct P2WSymbol {
/// Optional human-readable name, never used on-chain; makes
/// attester logs and the config easier to understand
name: Option<String>,
product: Pubkey,
price: Pubkey,
}
#[testmod]
mod tests {
#[test]
fn test_sanity() -> Result<(), ErrBox> {
let serialized = r#"
symbols:
- name: ETH/USD
product_addr: 11111111111111111111111111111111
price_addr: 11111111111111111111111111111111
- name: SOL/EUR
product_addr: 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi
price_addr: 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi
- name: BTC/CNY
product_addr: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
price_addr: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
- # no name
product_addr: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
price_addr: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
"#;
let deserialized = serde_yaml::from_str(serialized)?;
Ok(())
}
}

View File

@ -1,433 +0,0 @@
pub mod attestation_cfg;
pub mod cli;
use std::{
fs::File,
path::{
Path,
PathBuf,
},
};
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use clap::Clap;
use log::{
debug,
error,
info,
warn,
LevelFilter,
};
use solana_client::rpc_client::RpcClient;
use solana_program::{
hash::Hash,
instruction::{
AccountMeta,
Instruction,
},
pubkey::Pubkey,
system_program,
sysvar::{
clock,
rent,
},
};
use solana_sdk::{
commitment_config::CommitmentConfig,
signature::read_keypair_file,
transaction::Transaction,
};
use solana_transaction_status::UiTransactionEncoding;
use solitaire::{
processors::seeded::Seeded,
AccountState,
Derive,
Info,
};
use solitaire_client::{
AccEntry,
Keypair,
SolSigner,
ToInstruction,
};
use cli::{
Action,
Cli,
};
use bridge::{
accounts::{
Bridge,
FeeCollector,
Sequence,
SequenceDerivationData,
},
types::ConsistencyLevel,
CHAIN_ID_SOLANA,
};
use pyth2wormhole::{
attest::{
P2WEmitter,
P2W_MAX_BATCH_SIZE,
},
config::P2WConfigAccount,
initialize::InitializeAccounts,
set_config::SetConfigAccounts,
types::PriceAttestation,
AttestData,
Pyth2WormholeConfig,
};
use crate::attestation_cfg::AttestationConfig;
pub type ErrBox = Box<dyn std::error::Error>;
pub const SEQNO_PREFIX: &'static str = "Program log: Sequence: ";
fn main() -> Result<(), ErrBox> {
let cli = Cli::parse();
init_logging(cli.log_level);
let payer = read_keypair_file(&*shellexpand::tilde(&cli.payer))?;
let rpc_client = RpcClient::new_with_commitment(cli.rpc_url, CommitmentConfig::finalized());
let p2w_addr = cli.p2w_addr;
let latest_blockhash = rpc_client.get_latest_blockhash()?;
match cli.action {
Action::Init {
owner_addr,
pyth_owner_addr,
wh_prog,
} => {
let tx = handle_init(
payer,
p2w_addr,
owner_addr,
wh_prog,
pyth_owner_addr,
latest_blockhash,
)?;
rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
}
Action::SetConfig {
ref owner,
new_owner_addr,
new_wh_prog,
new_pyth_owner_addr,
} => {
let tx = handle_set_config(
payer,
p2w_addr,
read_keypair_file(&*shellexpand::tilde(&owner))?,
new_owner_addr,
new_wh_prog,
new_pyth_owner_addr,
latest_blockhash,
)?;
rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
}
Action::Attest {
ref attestation_cfg,
} => {
// Load the attestation config yaml
let attestation_cfg: AttestationConfig =
serde_yaml::from_reader(File::open(attestation_cfg)?)?;
handle_attest(&rpc_client, payer, p2w_addr, &attestation_cfg)?;
}
}
Ok(())
}
fn handle_init(
payer: Keypair,
p2w_addr: Pubkey,
new_owner_addr: Pubkey,
wh_prog: Pubkey,
pyth_owner_addr: Pubkey,
latest_blockhash: Hash,
) -> Result<Transaction, ErrBox> {
use AccEntry::*;
let payer_pubkey = payer.pubkey();
let accs = InitializeAccounts {
payer: Signer(payer),
new_config: Derived(p2w_addr),
};
let config = Pyth2WormholeConfig {
max_batch_size: P2W_MAX_BATCH_SIZE,
owner: new_owner_addr,
wh_prog: wh_prog,
pyth_owner: pyth_owner_addr,
};
let ix_data = (pyth2wormhole::instruction::Instruction::Initialize, config);
let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
&[ix],
Some(&payer_pubkey),
signers.iter().collect::<Vec<_>>().as_ref(),
latest_blockhash,
);
Ok(tx_signed)
}
fn handle_set_config(
payer: Keypair,
p2w_addr: Pubkey,
owner: Keypair,
new_owner_addr: Pubkey,
new_wh_prog: Pubkey,
new_pyth_owner_addr: Pubkey,
latest_blockhash: Hash,
) -> Result<Transaction, ErrBox> {
use AccEntry::*;
let payer_pubkey = payer.pubkey();
let accs = SetConfigAccounts {
payer: Signer(payer),
current_owner: Signer(owner),
config: Derived(p2w_addr),
};
let config = Pyth2WormholeConfig {
max_batch_size: P2W_MAX_BATCH_SIZE,
owner: new_owner_addr,
wh_prog: new_wh_prog,
pyth_owner: new_pyth_owner_addr,
};
let ix_data = (pyth2wormhole::instruction::Instruction::SetConfig, config);
let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
&[ix],
Some(&payer_pubkey),
signers.iter().collect::<Vec<_>>().as_ref(),
latest_blockhash,
);
Ok(tx_signed)
}
fn handle_attest(
rpc_client: &RpcClient, // Needed for reading Pyth account data
payer: Keypair,
p2w_addr: Pubkey,
attestation_cfg: &AttestationConfig,
) -> Result<(), ErrBox> {
// Derive seeded accounts
let emitter_addr = P2WEmitter::key(None, &p2w_addr);
info!("Using emitter addr {}", emitter_addr);
let p2w_config_addr = P2WConfigAccount::<{ AccountState::Initialized }>::key(None, &p2w_addr);
let config = Pyth2WormholeConfig::try_from_slice(
rpc_client.get_account_data(&p2w_config_addr)?.as_slice(),
)?;
let seq_addr = Sequence::key(
&SequenceDerivationData {
emitter_key: &emitter_addr,
},
&config.wh_prog,
);
// Read the current max batch size from the contract's settings
let max_batch_size = config.max_batch_size;
let batch_count = {
let whole_batches = attestation_cfg.symbols.len() / config.max_batch_size as usize;
// Include partial batch if there is a remainder
if attestation_cfg.symbols.len() % config.max_batch_size as usize > 0 {
whole_batches + 1
} else {
whole_batches
}
};
debug!("Symbol config:\n{:#?}", attestation_cfg);
info!(
"{} symbols read, max batch size {}, dividing into {} batches",
attestation_cfg.symbols.len(),
max_batch_size,
batch_count
);
let mut errors = Vec::new();
for (idx, symbols) in attestation_cfg
.symbols
.as_slice()
.chunks(max_batch_size as usize)
.enumerate()
{
let batch_no = idx + 1;
let sym_msg_keypair = Keypair::new();
info!(
"Batch {}/{} contents: {:?}",
batch_no,
batch_count,
symbols
.iter()
.map(|s| s
.name
.clone()
.unwrap_or(format!("unnamed product {:?}", s.product_addr)))
.collect::<Vec<_>>()
);
let mut sym_metas_vec: Vec<_> = symbols
.iter()
.map(|s| {
vec![
AccountMeta::new_readonly(s.product_addr, false),
AccountMeta::new_readonly(s.price_addr, false),
]
})
.flatten()
.collect();
// Align to max batch size with null accounts
let mut blank_accounts =
vec![
AccountMeta::new_readonly(Pubkey::new_from_array([0u8; 32]), false);
2 * (max_batch_size as usize - symbols.len())
];
sym_metas_vec.append(&mut blank_accounts);
// Arrange Attest accounts
let mut acc_metas = vec![
// payer
AccountMeta::new(payer.pubkey(), true),
// system_program
AccountMeta::new_readonly(system_program::id(), false),
// config
AccountMeta::new_readonly(p2w_config_addr, false),
];
// Insert max_batch_size metas
acc_metas.append(&mut sym_metas_vec);
// Continue with other pyth2wormhole accounts
let mut acc_metas_remainder = vec![
// clock
AccountMeta::new_readonly(clock::id(), false),
// wh_prog
AccountMeta::new_readonly(config.wh_prog, false),
// wh_bridge
AccountMeta::new(
Bridge::<{ AccountState::Initialized }>::key(None, &config.wh_prog),
false,
),
// wh_message
AccountMeta::new(sym_msg_keypair.pubkey(), true),
// wh_emitter
AccountMeta::new_readonly(emitter_addr, false),
// wh_sequence
AccountMeta::new(seq_addr, false),
// wh_fee_collector
AccountMeta::new(FeeCollector::<'_>::key(None, &config.wh_prog), false),
AccountMeta::new_readonly(rent::id(), false),
];
acc_metas.append(&mut acc_metas_remainder);
let ix_data = (
pyth2wormhole::instruction::Instruction::Attest,
AttestData {
consistency_level: ConsistencyLevel::Finalized,
},
);
let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
// Execute the transaction, obtain the resulting sequence
// number. The and_then() calls enforce error handling
// location near loop end.
let res = rpc_client
.get_latest_blockhash()
.and_then(|latest_blockhash| {
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
&[ix],
Some(&payer.pubkey()),
&vec![&payer, &sym_msg_keypair],
latest_blockhash,
);
rpc_client.send_and_confirm_transaction_with_spinner(&tx_signed)
})
.and_then(|sig| rpc_client.get_transaction(&sig, UiTransactionEncoding::Json))
.map_err(|e| -> ErrBox { e.into() })
.and_then(|this_tx| {
this_tx
.transaction
.meta
.and_then(|meta| meta.log_messages)
.and_then(|logs| {
let mut seqno = None;
for log in logs {
if log.starts_with(SEQNO_PREFIX) {
seqno = Some(log.replace(SEQNO_PREFIX, ""));
break;
}
}
seqno
})
.ok_or_else(|| format!("No seqno in program logs").into())
});
// Individual batch errors mustn't prevent other batches from being sent.
match res {
Ok(seqno) => {
println!("Sequence number: {}", seqno);
info!("Batch {}/{}: OK, seqno {}", batch_no, batch_count, seqno);
}
Err(e) => {
let msg = format!(
"Batch {}/{} tx error: {}",
batch_no,
batch_count,
e.to_string()
);
error!("{}", &msg);
errors.push(msg)
}
}
}
if errors.len() > 0 {
let err_list = errors.join("\n");
Err(format!("{} of {} batches failed:\n{}", errors.len(), batch_count, err_list).into())
} else {
Ok(())
}
}
fn init_logging(verbosity: u32) {
use LevelFilter::*;
let filter = match verbosity {
0..=1 => Error,
2 => Warn,
3 => Info,
4 => Debug,
_other => Trace,
};
env_logger::builder().filter_level(filter).init();
}

View File

@ -1,868 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "blake3"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if 0.1.10",
"constant_time_eq",
"crypto-mac",
"digest 0.9.0",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"block-padding",
"generic-array 0.14.4",
]
[[package]]
name = "block-padding"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "borsh"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74"
dependencies = [
"borsh-derive",
"hashbrown",
]
[[package]]
name = "borsh-derive"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd"
dependencies = [
"borsh-derive-internal",
"borsh-schema-derive-internal",
"proc-macro-crate",
"proc-macro2",
"syn",
]
[[package]]
name = "borsh-derive-internal"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "borsh-schema-derive-internal"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bridge"
version = "0.1.0"
dependencies = [
"borsh",
"byteorder",
"primitive-types",
"sha3",
"solana-program",
"solitaire",
]
[[package]]
name = "bs58"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb"
[[package]]
name = "bv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340"
dependencies = [
"feature-probe",
"serde",
]
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cpufeatures"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
dependencies = [
"libc",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-mac"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
dependencies = [
"generic-array 0.14.4",
"subtle",
]
[[package]]
name = "curve25519-dalek"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434e1720189a637d44fe464f4df1e6eb900b4835255b14354497c78af37d9bb8"
dependencies = [
"byteorder",
"digest 0.8.1",
"rand_core",
"subtle",
"zeroize",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "feature-probe"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da"
[[package]]
name = "fixed-hash"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
dependencies = [
"static_assertions",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"serde",
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
dependencies = [
"ahash",
]
[[package]]
name = "hermit-abi"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]]
name = "keccak"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "memmap2"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a"
dependencies = [
"libc",
]
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "primitive-types"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2415937401cb030a2a0a4d922483f945fa068f52a7dbb22ce0fe5f2b6f6adace"
dependencies = [
"fixed-hash",
"uint",
]
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
"toml",
]
[[package]]
name = "proc-macro2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
[[package]]
name = "pyth-client"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "731e2d5b2b790fc676518b29e41dddf7f69f23c61f27ab25cc9ae5b75ee190ad"
[[package]]
name = "pyth2wormhole"
version = "0.1.0"
dependencies = [
"borsh",
"bridge",
"pyth-client",
"rocksalt",
"solana-program",
"solitaire",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rocksalt"
version = "0.1.0"
dependencies = [
"byteorder",
"proc-macro2",
"quote",
"sha3",
"solana-program",
"syn",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_bytes"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sha2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
name = "sha3"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
dependencies = [
"block-buffer",
"digest 0.9.0",
"keccak",
"opaque-debug",
]
[[package]]
name = "solana-frozen-abi"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b81e60d88b1fe0322bba6f3fe6b0d7299df2f2ededa8d95ec77b934fabb967b"
dependencies = [
"bs58",
"bv",
"generic-array 0.14.4",
"log",
"memmap2",
"rustc_version",
"serde",
"serde_derive",
"sha2",
"solana-frozen-abi-macro",
"solana-logger",
"thiserror",
]
[[package]]
name = "solana-frozen-abi-macro"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f617daa0187bcc4665d63fcf9454c998e9cdad6a33181f6214558d738230bfe2"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "solana-logger"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b3e2b14bdcbb7b41de9ef5a541ac501ba3fbd07999cbcf7ea9006b3ae28b67b"
dependencies = [
"env_logger",
"lazy_static",
"log",
]
[[package]]
name = "solana-program"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c5d59f9d358c09db6461fae1fde6075a456685d856c004ef21af092a830e4e7"
dependencies = [
"bincode",
"blake3",
"borsh",
"borsh-derive",
"bs58",
"bv",
"curve25519-dalek",
"hex",
"itertools",
"lazy_static",
"log",
"num-derive",
"num-traits",
"rand",
"rustc_version",
"rustversion",
"serde",
"serde_bytes",
"serde_derive",
"sha2",
"sha3",
"solana-frozen-abi",
"solana-frozen-abi-macro",
"solana-logger",
"solana-sdk-macro",
"thiserror",
]
[[package]]
name = "solana-sdk-macro"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d27426b2a09676929c5e49df96967bbcffff003183c11a3c3ef11d78bac4aaaa"
dependencies = [
"bs58",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "solitaire"
version = "0.1.0"
dependencies = [
"borsh",
"byteorder",
"rocksalt",
"sha3",
"solana-program",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "subtle"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "syn"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "typenum"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "uint"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e"
dependencies = [
"byteorder",
"crunchy",
"hex",
"static_assertions",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "zeroize"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"

View File

@ -1,30 +0,0 @@
[package]
name = "pyth2wormhole"
version = "0.1.0"
description = "Pyth to Wormhole solana on-chain integration"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "pyth2wormhole"
[features]
default = ["wormhole-bridge-solana/no-entrypoint"]
client = ["solitaire/client", "solitaire-client", "no-entrypoint"]
trace = ["solitaire/trace", "wormhole-bridge-solana/trace"]
no-entrypoint = []
wasm = ["no-entrypoint", "wasm-bindgen", "serde", "serde_derive", "serde_json"]
[dependencies]
wormhole-bridge-solana = {path = "../../bridge/program"}
solitaire = { path = "../../solitaire/program" }
solitaire-client = { path = "../../solitaire/client", optional = true }
rocksalt = { path = "../../solitaire/rocksalt" }
solana-program = "=1.9.4"
borsh = "=0.9.1"
pyth-client = "0.2.2"
# Crates needed for easier wasm data passing
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"], optional = true}
serde = { version = "1", optional = true}
serde_derive = { version = "1", optional = true}
serde_json = { version = "1", optional = true}

View File

@ -1,279 +0,0 @@
use crate::{
config::P2WConfigAccount,
types::{
batch_serialize,
PriceAttestation,
},
};
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solana_program::{
clock::Clock,
instruction::{
AccountMeta,
Instruction,
},
program::{
invoke,
invoke_signed,
},
program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
};
use bridge::{
accounts::BridgeData,
types::ConsistencyLevel,
PostMessageData,
};
use solitaire::{
invoke_seeded,
trace,
AccountState,
Derive,
ExecutionContext,
FromAccounts,
Info,
InstructionContext,
Keyed,
Mut,
Peel,
Result as SoliResult,
Seeded,
Signer,
SolitaireError,
Sysvar,
ToInstruction,
};
pub type P2WEmitter<'b> = Derive<Info<'b>, "p2w-emitter">;
/// Important: must be manually maintained until native Solitaire
/// variable len vector support.
///
/// The number must reflect how many pyth product/price pairs are
/// expected in the Attest struct below. The constant itself is only
/// used in the on-chain config in order for attesters to learn the
/// correct value dynamically.
pub const P2W_MAX_BATCH_SIZE: u16 = 5;
#[derive(FromAccounts, ToInstruction)]
pub struct Attest<'b> {
// Payer also used for wormhole
pub payer: Mut<Signer<Info<'b>>>,
pub system_program: Info<'b>,
pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
// Hardcoded product/price pairs, bypassing Solitaire's variable-length limitations
// Any change to the number of accounts must include an appropriate change to P2W_MAX_BATCH_SIZE
pub pyth_product: Info<'b>,
pub pyth_price: Info<'b>,
pub pyth_product2: Option<Info<'b>>,
pub pyth_price2: Option<Info<'b>>,
pub pyth_product3: Option<Info<'b>>,
pub pyth_price3: Option<Info<'b>>,
pub pyth_product4: Option<Info<'b>>,
pub pyth_price4: Option<Info<'b>>,
pub pyth_product5: Option<Info<'b>>,
pub pyth_price5: Option<Info<'b>>,
// Did you read the comment near `pyth_product`?
// pub pyth_product6: Option<Info<'b>>,
// pub pyth_price6: Option<Info<'b>>,
// pub pyth_product7: Option<Info<'b>>,
// pub pyth_price7: Option<Info<'b>>,
// pub pyth_product8: Option<Info<'b>>,
// pub pyth_price8: Option<Info<'b>>,
// pub pyth_product9: Option<Info<'b>>,
// pub pyth_price9: Option<Info<'b>>,
// pub pyth_product10: Option<Info<'b>>,
// pub pyth_price10: Option<Info<'b>>,
pub clock: Sysvar<'b, Clock>,
/// Wormhole program address - must match the config value
pub wh_prog: Info<'b>,
// wormhole's post_message accounts
//
// This contract makes no attempt to exhaustively validate
// Wormhole's account inputs. Only the wormhole contract address
// is validated (see above).
/// Bridge config needed for fee calculation
pub wh_bridge: Mut<Info<'b>>,
/// Account to store the posted message
pub wh_message: Signer<Mut<Info<'b>>>,
/// Emitter of the VAA
pub wh_emitter: P2WEmitter<'b>,
/// Tracker for the emitter sequence
pub wh_sequence: Mut<Info<'b>>,
// We reuse our payer
// pub wh_payer: Mut<Signer<Info<'b>>>,
/// Account to collect tx fee
pub wh_fee_collector: Mut<Info<'b>>,
pub wh_rent: Sysvar<'b, Rent>,
}
#[derive(BorshDeserialize, BorshSerialize)]
pub struct AttestData {
pub consistency_level: ConsistencyLevel,
}
impl<'b> InstructionContext<'b> for Attest<'b> {
fn deps(&self) -> Vec<Pubkey> {
vec![solana_program::system_program::id()]
}
}
pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> SoliResult<()> {
accs.config.verify_derivation(ctx.program_id, None)?;
if accs.config.wh_prog != *accs.wh_prog.key {
trace!(&format!(
"Wormhole program account mismatch (expected {:?}, got {:?})",
accs.config.wh_prog, accs.wh_prog.key
));
return Err(ProgramError::InvalidAccountData.into());
}
// Make the specified prices iterable
let price_pair_opts = [
Some(&accs.pyth_product),
Some(&accs.pyth_price),
accs.pyth_product2.as_ref(),
accs.pyth_price2.as_ref(),
accs.pyth_product3.as_ref(),
accs.pyth_price3.as_ref(),
accs.pyth_product4.as_ref(),
accs.pyth_price4.as_ref(),
accs.pyth_product5.as_ref(),
accs.pyth_price5.as_ref(),
// Did you read the comment near `pyth_product`?
// accs.pyth_product6.as_ref(),
// accs.pyth_price6.as_ref(),
// accs.pyth_product7.as_ref(),
// accs.pyth_price7.as_ref(),
// accs.pyth_product8.as_ref(),
// accs.pyth_price8.as_ref(),
// accs.pyth_product9.as_ref(),
// accs.pyth_price9.as_ref(),
// accs.pyth_product10.as_ref(),
// accs.pyth_price10.as_ref(),
];
let price_pairs: Vec<_> = price_pair_opts.into_iter().filter_map(|acc| *acc).collect();
if price_pairs.len() % 2 != 0 {
trace!(&format!(
"Uneven product/price count detected: {}",
price_pairs.len()
));
return Err(ProgramError::InvalidAccountData.into());
}
trace!("{} Pyth symbols received", price_pairs.len() / 2);
// Collect the validated symbols for batch serialization
let mut attestations = Vec::with_capacity(price_pairs.len() / 2);
for pair in price_pairs.as_slice().chunks_exact(2) {
let product = pair[0];
let price = pair[1];
if accs.config.pyth_owner != *price.owner || accs.config.pyth_owner != *product.owner {
trace!(&format!(
"Pair {:?} - {:?}: pyth_owner pubkey mismatch (expected {:?}, got product owner {:?} and price owner {:?}",
product, price,
accs.config.pyth_owner, product.owner, price.owner
));
return Err(SolitaireError::InvalidOwner(accs.pyth_price.owner.clone()).into());
}
let attestation = PriceAttestation::from_pyth_price_bytes(
price.key.clone(),
accs.clock.unix_timestamp,
&*price.try_borrow_data()?,
)?;
// The following check is crucial against poorly ordered
// account inputs, e.g. [Some(prod1), Some(price1),
// Some(prod2), None, None, Some(price)], interpreted by
// earlier logic as [(prod1, price1), (prod2, price3)].
//
// Failing to verify the product/price relationship could lead
// to mismatched product/price metadata, which would result in
// a false attestation.
if &attestation.product_id != product.key {
trace!(&format!(
"Price's product_id does not match the pased account (points at {:?} instead)",
attestation.product_id
));
return Err(ProgramError::InvalidAccountData.into());
}
attestations.push(attestation);
}
trace!("Attestations successfully created");
let bridge_config = BridgeData::try_from_slice(&accs.wh_bridge.try_borrow_mut_data()?)?.config;
// Pay wormhole fee
let transfer_ix = solana_program::system_instruction::transfer(
accs.payer.key,
accs.wh_fee_collector.info().key,
bridge_config.fee,
);
solana_program::program::invoke(&transfer_ix, ctx.accounts)?;
// Send payload
let post_message_data = (
bridge::instruction::Instruction::PostMessage,
PostMessageData {
nonce: 0, // Superseded by the sequence number
payload: batch_serialize(attestations.as_slice().iter()).map_err(|e| {
trace!(e.to_string());
ProgramError::InvalidAccountData
})?,
consistency_level: data.consistency_level,
},
);
let ix = Instruction::new_with_bytes(
accs.config.wh_prog,
post_message_data.try_to_vec()?.as_slice(),
vec![
AccountMeta::new(*accs.wh_bridge.key, false),
AccountMeta::new(*accs.wh_message.key, true),
AccountMeta::new_readonly(*accs.wh_emitter.key, true),
AccountMeta::new(*accs.wh_sequence.key, false),
AccountMeta::new(*accs.payer.key, true),
AccountMeta::new(*accs.wh_fee_collector.key, false),
AccountMeta::new_readonly(*accs.clock.info().key, false),
AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
);
trace!("Before cross-call");
invoke_seeded(&ix, ctx, &accs.wh_emitter, None)?;
Ok(())
}

View File

@ -1,44 +0,0 @@
//! On-chain state for the pyth2wormhole SOL contract.
//!
//! Important: A config init/update should be performed on every
//! deployment/upgrade of this Solana program. Doing so prevents
//! problems related to max batch size mismatches between config and
//! contract logic. See attest.rs for details.
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solana_program::pubkey::Pubkey;
use solitaire::{
processors::seeded::AccountOwner,
AccountState,
Data,
Derive,
Owned,
};
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct Pyth2WormholeConfig {
/// Authority owning this contract
pub owner: Pubkey,
/// Wormhole bridge program
pub wh_prog: Pubkey,
/// Authority owning Pyth price data
pub pyth_owner: Pubkey,
/// How many product/price pairs can be sent and attested at once
///
/// Important: Whenever the corresponding logic in attest.rs
/// changes its expected number of symbols per batch, this config
/// must be updated accordingly on-chain.
pub max_batch_size: u16,
}
impl Owned for Pyth2WormholeConfig {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
pub type P2WConfigAccount<'b, const IsInitialized: AccountState> =
Derive<Data<'b, Pyth2WormholeConfig, { IsInitialized }>, "pyth2wormhole-config">;

View File

@ -1,45 +0,0 @@
use solana_program::pubkey::Pubkey;
use solitaire::{
AccountState,
CreationLamports,
ExecutionContext,
FromAccounts,
Info,
InstructionContext,
Keyed,
Mut,
Peel,
Result as SoliResult,
Signer,
ToInstruction,
};
use crate::config::{
P2WConfigAccount,
Pyth2WormholeConfig,
};
#[derive(FromAccounts, ToInstruction)]
pub struct Initialize<'b> {
pub new_config: Mut<P2WConfigAccount<'b, { AccountState::Uninitialized }>>,
pub payer: Mut<Signer<Info<'b>>>,
}
impl<'b> InstructionContext<'b> for Initialize<'b> {
fn deps(&self) -> Vec<Pubkey> {
vec![]
}
}
/// Must be called right after deployment
pub fn initialize(
ctx: &ExecutionContext,
accs: &mut Initialize,
data: Pyth2WormholeConfig,
) -> SoliResult<()> {
accs.new_config
.create(ctx, accs.payer.info().key, CreationLamports::Exempt)?;
accs.new_config.1 = data;
Ok(())
}

View File

@ -1,33 +0,0 @@
#![feature(adt_const_params)]
pub mod attest;
pub mod config;
pub mod initialize;
pub mod set_config;
pub mod types;
use solitaire::solitaire;
pub use attest::{
attest,
Attest,
AttestData,
};
pub use config::Pyth2WormholeConfig;
pub use initialize::{
initialize,
Initialize,
};
pub use set_config::{
set_config,
SetConfig,
};
solitaire! {
Attest(AttestData) => attest,
Initialize(Pyth2WormholeConfig) => initialize,
SetConfig(Pyth2WormholeConfig) => set_config,
}
#[cfg(feature = "wasm")]
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub mod wasm;

View File

@ -1,60 +0,0 @@
use solana_program::{
msg,
pubkey::Pubkey,
};
use solitaire::{
AccountState,
ExecutionContext,
FromAccounts,
Info,
InstructionContext,
Keyed,
Mut,
Peel,
Result as SoliResult,
Signer,
SolitaireError,
ToInstruction,
};
use crate::config::{
P2WConfigAccount,
Pyth2WormholeConfig,
};
#[derive(FromAccounts, ToInstruction)]
pub struct SetConfig<'b> {
/// Current config used by the program
pub config: Mut<P2WConfigAccount<'b, { AccountState::Initialized }>>,
/// Current owner authority of the program
pub current_owner: Mut<Signer<Info<'b>>>,
/// Payer account for updating the account data
pub payer: Mut<Signer<Info<'b>>>,
}
impl<'b> InstructionContext<'b> for SetConfig<'b> {
fn deps(&self) -> Vec<Pubkey> {
vec![]
}
}
/// Alters the current settings of pyth2wormhole
pub fn set_config(
_ctx: &ExecutionContext,
accs: &mut SetConfig,
data: Pyth2WormholeConfig,
) -> SoliResult<()> {
if &accs.config.0.owner != accs.current_owner.info().key {
msg!(
"Current owner account mismatch (expected {:?})",
accs.config.0.owner
);
return Err(SolitaireError::InvalidSigner(
accs.current_owner.info().key.clone(),
));
}
accs.config.1 = data;
Ok(())
}

View File

@ -1,653 +0,0 @@
//! Constants and values common to every p2w custom-serialized message.
//!
//! The format makes no attempt to provide human-readable symbol names
//! in favor of explicit product/price Solana account addresses
//! (IDs). This choice was made to disambiguate any symbols with
//! similar human-readable names and provide a failsafe for some of
//! the probable adversarial scenarios.
pub mod pyth_extensions;
use std::{
borrow::Borrow,
convert::{
TryFrom,
TryInto,
},
io::Read,
iter::Iterator,
mem,
};
use borsh::BorshSerialize;
use pyth_client::{
AccountType,
CorpAction,
Ema,
Price,
PriceStatus,
PriceType,
};
use solana_program::{
clock::UnixTimestamp,
program_error::ProgramError,
pubkey::Pubkey,
};
use solitaire::{
trace,
ErrBox,
Result as SoliResult,
SolitaireError,
};
use self::pyth_extensions::{
P2WCorpAction,
P2WEma,
P2WPriceStatus,
P2WPriceType,
};
/// Precedes every message implementing the p2w serialization format
pub const P2W_MAGIC: &'static [u8] = b"P2WH";
/// Format version used and understood by this codebase
pub const P2W_FORMAT_VERSION: u16 = 2;
pub const PUBKEY_LEN: usize = 32;
/// Decides the format of following bytes
#[repr(u8)]
pub enum PayloadId {
PriceAttestation = 1, // Not in use, currently batch attestations imply PriceAttestation messages inside
PriceBatchAttestation,
}
// On-chain data types
/// The main attestation data type.
///
/// Important: For maximum security, *both* product_id and price_id
/// should be used as storage keys for known attestations in target
/// chain logic.
#[derive(Clone, Default, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "wasm",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub struct PriceAttestation {
pub product_id: Pubkey,
pub price_id: Pubkey,
pub price_type: P2WPriceType,
pub price: i64,
pub expo: i32,
pub twap: P2WEma,
pub twac: P2WEma,
pub confidence_interval: u64,
pub status: P2WPriceStatus,
pub corp_act: P2WCorpAction,
pub timestamp: UnixTimestamp,
}
/// Turn a bunch of attestations into a combined payload.
///
/// Batches assume constant-size attestations within a single batch.
pub fn batch_serialize(
attestations: impl Iterator<Item = impl Borrow<PriceAttestation>>,
) -> Result<Vec<u8>, ErrBox> {
// magic
let mut buf = P2W_MAGIC.to_vec();
// version
buf.extend_from_slice(&P2W_FORMAT_VERSION.to_be_bytes()[..]);
// payload_id
buf.push(PayloadId::PriceBatchAttestation as u8);
let collected: Vec<_> = attestations.collect();
// n_attestations
buf.extend_from_slice(&(collected.len() as u16).to_be_bytes()[..]);
let mut attestation_size = 0; // Will be determined as we serialize attestations
let mut serialized_attestations = Vec::with_capacity(collected.len());
for (idx, a) in collected.iter().enumerate() {
// Learn the current attestation's size
let serialized = PriceAttestation::serialize(a.borrow());
let a_len = serialized.len();
// Verify it's the same as the first one we saw for the batch, assign if we're first.
if attestation_size > 0 {
if a_len != attestation_size {
return Err(format!(
"attestation {} serializes to {} bytes, {} expected",
idx + 1,
a_len,
attestation_size
)
.into());
}
} else {
attestation_size = a_len;
}
serialized_attestations.push(serialized);
}
// attestation_size
buf.extend_from_slice(&(attestation_size as u16).to_be_bytes()[..]);
for mut s in serialized_attestations.into_iter() {
buf.append(&mut s)
}
Ok(buf)
}
/// Undo `batch_serialize`
pub fn batch_deserialize(mut bytes: impl Read) -> Result<Vec<PriceAttestation>, ErrBox> {
let mut magic_vec = vec![0u8; P2W_MAGIC.len()];
bytes.read_exact(magic_vec.as_mut_slice())?;
if magic_vec.as_slice() != P2W_MAGIC {
return Err(format!(
"Invalid magic {:02X?}, expected {:02X?}",
magic_vec, P2W_MAGIC,
)
.into());
}
let mut version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VERSION)];
bytes.read_exact(version_vec.as_mut_slice())?;
let version = u16::from_be_bytes(version_vec.as_slice().try_into()?);
if version != P2W_FORMAT_VERSION {
return Err(format!(
"Unsupported format version {}, expected {}",
version, P2W_FORMAT_VERSION
)
.into());
}
let mut payload_id_vec = vec![0u8; mem::size_of::<PayloadId>()];
bytes.read_exact(payload_id_vec.as_mut_slice())?;
if payload_id_vec[0] != PayloadId::PriceBatchAttestation as u8 {
return Err(format!(
"Invalid Payload ID {}, expected {}",
payload_id_vec[0],
PayloadId::PriceBatchAttestation as u8,
)
.into());
}
let mut batch_len_vec = vec![0u8; 2];
bytes.read_exact(batch_len_vec.as_mut_slice())?;
let batch_len = u16::from_be_bytes(batch_len_vec.as_slice().try_into()?);
let mut attestation_size_vec = vec![0u8; 2];
bytes.read_exact(attestation_size_vec.as_mut_slice())?;
let attestation_size = u16::from_be_bytes(attestation_size_vec.as_slice().try_into()?);
let mut ret = Vec::with_capacity(batch_len as usize);
for i in 0..batch_len {
let mut attestation_buf = vec![0u8; attestation_size as usize];
bytes.read_exact(attestation_buf.as_mut_slice())?;
dbg!(&attestation_buf.len());
match PriceAttestation::deserialize(attestation_buf.as_slice()) {
Ok(attestation) => ret.push(attestation),
Err(e) => return Err(format!("PriceAttestation {}/{}: {}", i + 1, batch_len, e).into()),
}
}
Ok(ret)
}
impl PriceAttestation {
pub fn from_pyth_price_bytes(
price_id: Pubkey,
timestamp: UnixTimestamp,
value: &[u8],
) -> Result<Self, SolitaireError> {
let price = parse_pyth_price(value)?;
Ok(PriceAttestation {
product_id: Pubkey::new(&price.prod.val[..]),
price_id,
price_type: (&price.ptype).into(),
price: price.agg.price,
twap: (&price.twap).into(),
twac: (&price.twac).into(),
expo: price.expo,
confidence_interval: price.agg.conf,
status: (&price.agg.status).into(),
corp_act: (&price.agg.corp_act).into(),
timestamp: timestamp,
})
}
/// Serialize this attestation according to the Pyth-over-wormhole serialization format
pub fn serialize(&self) -> Vec<u8> {
// A nifty trick to get us yelled at if we forget to serialize a field
#[deny(warnings)]
let PriceAttestation {
product_id,
price_id,
price_type,
price,
expo,
twap,
twac,
confidence_interval,
status,
corp_act,
timestamp,
} = self;
// magic
let mut buf = P2W_MAGIC.to_vec();
// version
buf.extend_from_slice(&P2W_FORMAT_VERSION.to_be_bytes()[..]);
// payload_id
buf.push(PayloadId::PriceAttestation as u8);
// product_id
buf.extend_from_slice(&product_id.to_bytes()[..]);
// price_id
buf.extend_from_slice(&price_id.to_bytes()[..]);
// price_type
buf.push(price_type.clone() as u8);
// price
buf.extend_from_slice(&price.to_be_bytes()[..]);
// exponent
buf.extend_from_slice(&expo.to_be_bytes()[..]);
// twap
buf.append(&mut twap.serialize());
// twac
buf.append(&mut twac.serialize());
// confidence_interval
buf.extend_from_slice(&confidence_interval.to_be_bytes()[..]);
// status
buf.push(status.clone() as u8);
// corp_act
buf.push(corp_act.clone() as u8);
// timestamp
buf.extend_from_slice(&timestamp.to_be_bytes()[..]);
buf
}
pub fn deserialize(mut bytes: impl Read) -> Result<Self, ErrBox> {
use P2WCorpAction::*;
use P2WPriceStatus::*;
use P2WPriceType::*;
let mut magic_vec = vec![0u8; P2W_MAGIC.len()];
bytes.read_exact(magic_vec.as_mut_slice())?;
if magic_vec.as_slice() != P2W_MAGIC {
return Err(format!(
"Invalid magic {:02X?}, expected {:02X?}",
magic_vec, P2W_MAGIC,
)
.into());
}
let mut version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VERSION)];
bytes.read_exact(version_vec.as_mut_slice())?;
let version = u16::from_be_bytes(version_vec.as_slice().try_into()?);
if version != P2W_FORMAT_VERSION {
return Err(format!(
"Unsupported format version {}, expected {}",
version, P2W_FORMAT_VERSION
)
.into());
}
let mut payload_id_vec = vec![0u8; mem::size_of::<PayloadId>()];
bytes.read_exact(payload_id_vec.as_mut_slice())?;
if PayloadId::PriceAttestation as u8 != payload_id_vec[0] {
return Err(format!(
"Invalid Payload ID {}, expected {}",
payload_id_vec[0],
PayloadId::PriceAttestation as u8,
)
.into());
}
let mut product_id_vec = vec![0u8; PUBKEY_LEN];
bytes.read_exact(product_id_vec.as_mut_slice())?;
let product_id = Pubkey::new(product_id_vec.as_slice());
let mut price_id_vec = vec![0u8; PUBKEY_LEN];
bytes.read_exact(price_id_vec.as_mut_slice())?;
let price_id = Pubkey::new(price_id_vec.as_slice());
let mut price_type_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
bytes.read_exact(price_type_vec.as_mut_slice())?;
let price_type = match price_type_vec[0] {
a if a == Price as u8 => Price,
a if a == P2WPriceType::Unknown as u8 => P2WPriceType::Unknown,
other => {
return Err(format!("Invalid price_type value {}", other).into());
}
};
let mut price_vec = vec![0u8; mem::size_of::<i64>()];
bytes.read_exact(price_vec.as_mut_slice())?;
let price = i64::from_be_bytes(price_vec.as_slice().try_into()?);
let mut expo_vec = vec![0u8; mem::size_of::<i32>()];
bytes.read_exact(expo_vec.as_mut_slice())?;
let expo = i32::from_be_bytes(expo_vec.as_slice().try_into()?);
let twap = P2WEma::deserialize(&mut bytes)?;
let twac = P2WEma::deserialize(&mut bytes)?;
println!("twac OK");
let mut confidence_interval_vec = vec![0u8; mem::size_of::<u64>()];
bytes.read_exact(confidence_interval_vec.as_mut_slice())?;
let confidence_interval =
u64::from_be_bytes(confidence_interval_vec.as_slice().try_into()?);
let mut status_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
bytes.read_exact(status_vec.as_mut_slice())?;
let status = match status_vec[0] {
a if a == P2WPriceStatus::Unknown as u8 => P2WPriceStatus::Unknown,
a if a == Trading as u8 => Trading,
a if a == Halted as u8 => Halted,
a if a == Auction as u8 => Auction,
other => {
return Err(format!("Invalid status value {}", other).into());
}
};
let mut corp_act_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
bytes.read_exact(corp_act_vec.as_mut_slice())?;
let corp_act = match corp_act_vec[0] {
a if a == NoCorpAct as u8 => NoCorpAct,
other => {
return Err(format!("Invalid corp_act value {}", other).into());
}
};
let mut timestamp_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
bytes.read_exact(timestamp_vec.as_mut_slice())?;
let timestamp = UnixTimestamp::from_be_bytes(timestamp_vec.as_slice().try_into()?);
Ok(Self {
product_id,
price_id,
price_type,
price,
expo,
twap,
twac,
confidence_interval,
status,
corp_act,
timestamp,
})
}
}
/// Deserializes Price from raw bytes, sanity-check.
fn parse_pyth_price(price_data: &[u8]) -> SoliResult<&Price> {
if price_data.len() != mem::size_of::<Price>() {
trace!(&format!(
"parse_pyth_price: buffer length mismatch ({} expected, got {})",
mem::size_of::<Price>(),
price_data.len()
));
return Err(ProgramError::InvalidAccountData.into());
}
let price_account = pyth_client::cast::<Price>(price_data);
if price_account.atype != AccountType::Price as u32 {
trace!(&format!(
"parse_pyth_price: AccountType mismatch ({} expected, got {})",
mem::size_of::<Price>(),
price_data.len()
));
return Err(ProgramError::InvalidAccountData.into());
}
Ok(price_account)
}
#[cfg(test)]
mod tests {
use super::*;
use pyth_client::{
AccKey,
AccountType,
PriceComp,
PriceInfo,
};
macro_rules! empty_acckey {
() => {
AccKey { val: [0u8; 32] }
};
}
macro_rules! empty_priceinfo {
() => {
PriceInfo {
price: 0,
conf: 0,
status: PriceStatus::Unknown,
corp_act: CorpAction::NoCorpAct,
pub_slot: 0,
}
};
}
macro_rules! empty_pricecomp {
() => {
PriceComp {
publisher: empty_acckey!(),
agg: empty_priceinfo!(),
latest: empty_priceinfo!(),
}
};
}
macro_rules! empty_ema {
() => {
(&P2WEma::default()).into()
};
}
macro_rules! empty_price {
() => {
Price {
magic: pyth_client::MAGIC,
ver: pyth_client::VERSION,
atype: AccountType::Price as u32,
size: 0,
ptype: PriceType::Price,
expo: 0,
num: 0,
num_qt: 0,
last_slot: 0,
valid_slot: 0,
drv1: 0,
drv2: 0,
drv3: 0,
twap: empty_ema!(),
twac: empty_ema!(),
prod: empty_acckey!(),
next: empty_acckey!(),
prev_slot: 0, // valid slot of previous update
prev_price: 0, // aggregate price of previous update
prev_conf: 0, // confidence interval of previous update
agg: empty_priceinfo!(),
// A nice macro might come in handy if this gets annoying
comp: [
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
empty_pricecomp!(),
],
}
};
}
fn mock_attestation(prod: Option<[u8; 32]>, price: Option<[u8; 32]>) -> PriceAttestation {
let product_id_bytes = prod.unwrap_or([21u8; 32]);
let price_id_bytes = prod.unwrap_or([222u8; 32]);
PriceAttestation {
product_id: Pubkey::new_from_array(product_id_bytes),
price_id: Pubkey::new_from_array(price_id_bytes),
price: (0xdeadbeefdeadbabe as u64) as i64,
price_type: P2WPriceType::Price,
twap: P2WEma {
val: -42,
numer: 15,
denom: 37,
},
twac: P2WEma {
val: 42,
numer: 1111,
denom: 2222,
},
expo: -3,
status: P2WPriceStatus::Trading,
confidence_interval: 101,
corp_act: P2WCorpAction::NoCorpAct,
timestamp: 123456789i64,
}
}
#[test]
fn test_parse_pyth_price_wrong_size_slices() {
assert!(parse_pyth_price(&[]).is_err());
assert!(parse_pyth_price(vec![0u8; 1].as_slice()).is_err());
}
#[test]
fn test_parse_pyth_price() -> SoliResult<()> {
let price = Price {
expo: 5,
agg: PriceInfo {
price: 42,
..empty_priceinfo!()
},
..empty_price!()
};
let price_vec = vec![price];
// use the C repr to mock pyth's format
let (_, bytes, _) = unsafe { price_vec.as_slice().align_to::<u8>() };
parse_pyth_price(bytes)?;
Ok(())
}
#[test]
fn test_attestation_serde() -> Result<(), ErrBox> {
let product_id_bytes = [21u8; 32];
let price_id_bytes = [222u8; 32];
let attestation: PriceAttestation =
mock_attestation(Some(product_id_bytes), Some(price_id_bytes));
println!("Hex product_id: {:02X?}", &product_id_bytes);
println!("Hex price_id: {:02X?}", &price_id_bytes);
println!("Regular: {:#?}", &attestation);
println!("Hex: {:#02X?}", &attestation);
let bytes = attestation.serialize();
println!("Hex Bytes: {:02X?}", bytes);
assert_eq!(
PriceAttestation::deserialize(bytes.as_slice())?,
attestation
);
Ok(())
}
#[test]
fn test_attestation_serde_wrong_size() -> Result<(), ErrBox> {
assert!(PriceAttestation::deserialize(&[][..]).is_err());
assert!(PriceAttestation::deserialize(vec![0u8; 1].as_slice()).is_err());
Ok(())
}
#[test]
fn test_batch_serde() -> Result<(), ErrBox> {
let attestations: Vec<_> = (0..65535)
.map(|i| mock_attestation(Some([(i % 256) as u8; 32]), None))
.collect();
let serialized = batch_serialize(attestations.iter())?;
let deserialized = batch_deserialize(serialized.as_slice())?;
assert_eq!(attestations, deserialized);
Ok(())
}
#[test]
fn test_batch_serde_wrong_size() -> Result<(), ErrBox> {
assert!(batch_deserialize(&[][..]).is_err());
assert!(batch_deserialize(vec![0u8; 1].as_slice()).is_err());
let attestations: Vec<_> = (0..20)
.map(|i| mock_attestation(Some([(i % 256) as u8; 32]), None))
.collect();
let serialized = batch_serialize(attestations.iter())?;
// Missing last byte in last attestation must be an error
let len = serialized.len();
assert!(batch_deserialize(&serialized.as_slice()[..len - 1]).is_err());
Ok(())
}
}

View File

@ -1,176 +0,0 @@
//! This module contains 1:1 (or close) copies of selected Pyth types
//! with quick and dirty enhancements.
use std::{
convert::TryInto,
io::Read,
mem,
};
use pyth_client::{
CorpAction,
Ema,
PriceStatus,
PriceType,
};
use solitaire::ErrBox;
/// 1:1 Copy of pyth_client::PriceType with derived additional traits.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "wasm",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[repr(u8)]
pub enum P2WPriceType {
Unknown,
Price,
}
impl From<&PriceType> for P2WPriceType {
fn from(pt: &PriceType) -> Self {
match pt {
PriceType::Unknown => Self::Unknown,
PriceType::Price => Self::Price,
}
}
}
impl Default for P2WPriceType {
fn default() -> Self {
Self::Price
}
}
/// 1:1 Copy of pyth_client::PriceStatus with derived additional traits.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "wasm",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub enum P2WPriceStatus {
Unknown,
Trading,
Halted,
Auction,
}
impl From<&PriceStatus> for P2WPriceStatus {
fn from(ps: &PriceStatus) -> Self {
match ps {
PriceStatus::Unknown => Self::Unknown,
PriceStatus::Trading => Self::Trading,
PriceStatus::Halted => Self::Halted,
PriceStatus::Auction => Self::Auction,
}
}
}
impl Default for P2WPriceStatus {
fn default() -> Self {
Self::Trading
}
}
/// 1:1 Copy of pyth_client::CorpAction with derived additional traits.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "wasm",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub enum P2WCorpAction {
NoCorpAct,
}
impl Default for P2WCorpAction {
fn default() -> Self {
Self::NoCorpAct
}
}
impl From<&CorpAction> for P2WCorpAction {
fn from(ca: &CorpAction) -> Self {
match ca {
CorpAction::NoCorpAct => P2WCorpAction::NoCorpAct,
}
}
}
/// 1:1 Copy of pyth_client::Ema with all-pub fields.
#[derive(Clone, Default, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "wasm",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[repr(C)]
pub struct P2WEma {
pub val: i64,
pub numer: i64,
pub denom: i64,
}
/// CAUTION: This impl may panic and requires an unsafe cast
impl From<&Ema> for P2WEma {
fn from(ema: &Ema) -> Self {
let our_size = mem::size_of::<P2WEma>();
let upstream_size = mem::size_of::<Ema>();
if our_size == upstream_size {
unsafe { std::mem::transmute_copy(ema) }
} else {
dbg!(our_size);
dbg!(upstream_size);
// Because of private upstream fields it's impossible to
// complain about type-level changes at compile-time
panic!("P2WEma sizeof mismatch")
}
}
}
/// CAUTION: This impl may panic and requires an unsafe cast
impl Into<Ema> for &P2WEma {
fn into(self) -> Ema {
let our_size = mem::size_of::<P2WEma>();
let upstream_size = mem::size_of::<Ema>();
if our_size == upstream_size {
unsafe { std::mem::transmute_copy(self) }
} else {
dbg!(our_size);
dbg!(upstream_size);
// Because of private upstream fields it's impossible to
// complain about type-level changes at compile-time
panic!("P2WEma sizeof mismatch")
}
}
}
impl P2WEma {
pub fn serialize(&self) -> Vec<u8> {
let mut v = vec![];
// val
v.extend(&self.val.to_be_bytes()[..]);
// numer
v.extend(&self.numer.to_be_bytes()[..]);
// denom
v.extend(&self.denom.to_be_bytes()[..]);
v
}
pub fn deserialize(mut bytes: impl Read) -> Result<Self, ErrBox> {
let mut val_vec = vec![0u8; mem::size_of::<i64>()];
bytes.read_exact(val_vec.as_mut_slice())?;
let val = i64::from_be_bytes(val_vec.as_slice().try_into()?);
let mut numer_vec = vec![0u8; mem::size_of::<i64>()];
bytes.read_exact(numer_vec.as_mut_slice())?;
let numer = i64::from_be_bytes(numer_vec.as_slice().try_into()?);
let mut denom_vec = vec![0u8; mem::size_of::<i64>()];
bytes.read_exact(denom_vec.as_mut_slice())?;
let denom = i64::from_be_bytes(denom_vec.as_slice().try_into()?);
Ok(Self { val, numer, denom })
}
}

View File

@ -1,32 +0,0 @@
use solana_program::pubkey::Pubkey;
use solitaire::Seeded;
use wasm_bindgen::prelude::*;
use std::str::FromStr;
use crate::{
attest::P2WEmitter,
types,
};
#[wasm_bindgen]
pub fn get_emitter_address(program_id: String) -> Vec<u8> {
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
let emitter = P2WEmitter::key(None, &program_id);
emitter.to_bytes().to_vec()
}
#[wasm_bindgen]
pub fn parse_attestation(bytes: Vec<u8>) -> JsValue {
let a = types::PriceAttestation::deserialize(bytes.as_slice()).unwrap();
JsValue::from_serde(&a).unwrap()
}
#[wasm_bindgen]
pub fn parse_batch_attestation(bytes: Vec<u8>) -> JsValue {
let a = types::batch_deserialize(bytes.as_slice()).unwrap();
JsValue::from_serde(&a).unwrap()
}

559
terra/Cargo.lock generated
View File

@ -17,44 +17,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
@ -69,7 +31,7 @@ checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6"
dependencies = [
"addr2line",
"cc",
"cfg-if 1.0.0",
"cfg-if",
"libc",
"miniz_oxide",
"object 0.27.1",
@ -92,36 +54,12 @@ dependencies = [
"crunchy 0.1.6",
]
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake3"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if 0.1.10",
"constant_time_eq",
"crypto-mac 0.8.0",
"digest 0.9.0",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
@ -129,7 +67,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"block-padding",
"generic-array 0.14.4",
"generic-array",
]
[[package]]
@ -138,67 +76,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "borsh"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74"
dependencies = [
"borsh-derive",
"hashbrown 0.9.1",
]
[[package]]
name = "borsh-derive"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd"
dependencies = [
"borsh-derive-internal",
"borsh-schema-derive-internal",
"proc-macro-crate",
"proc-macro2",
"syn",
]
[[package]]
name = "borsh-derive-internal"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "borsh-schema-derive-internal"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bs58"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb"
[[package]]
name = "bv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340"
dependencies = [
"feature-probe",
"serde",
]
[[package]]
name = "byteorder"
version = "1.4.3"
@ -211,12 +88,6 @@ version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -235,19 +106,13 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cosmwasm-crypto"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec9bdd1f4da5fc0d085251b0322661c5aaf773ab299e3e205fb18130b7f6ba3"
dependencies = [
"digest 0.9.0",
"digest",
"ed25519-zebra",
"k256",
"rand_core 0.5.1",
@ -395,7 +260,7 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@ -404,7 +269,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"crossbeam-utils",
]
@ -414,7 +279,7 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
@ -425,7 +290,7 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"crossbeam-utils",
"lazy_static",
"memoffset",
@ -438,7 +303,7 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"lazy_static",
]
@ -460,45 +325,22 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
dependencies = [
"generic-array 0.14.4",
"generic-array",
"rand_core 0.6.3",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-mac"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
dependencies = [
"generic-array 0.14.4",
"subtle",
]
[[package]]
name = "crypto-mac"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array 0.14.4",
"generic-array",
"subtle",
]
[[package]]
name = "curve25519-dalek"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216"
dependencies = [
"byteorder",
"digest 0.8.1",
"rand_core 0.5.1",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek"
version = "3.2.0"
@ -506,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
"byteorder",
"digest 0.9.0",
"digest",
"rand_core 0.5.1",
"subtle",
"zeroize",
@ -693,22 +535,13 @@ dependencies = [
"const-oid",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.4",
"generic-array",
]
[[package]]
@ -761,7 +594,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
dependencies = [
"curve25519-dalek 3.2.0",
"curve25519-dalek",
"hex",
"rand_core 0.5.1",
"serde",
@ -783,7 +616,7 @@ checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
dependencies = [
"crypto-bigint",
"ff",
"generic-array 0.14.4",
"generic-array",
"group",
"pkcs8",
"rand_core 0.6.3",
@ -812,31 +645,12 @@ dependencies = [
"syn",
]
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "feature-probe"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da"
[[package]]
name = "ff"
version = "0.10.1"
@ -853,22 +667,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"serde",
"typenum",
"version_check",
]
@ -879,7 +683,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
@ -890,7 +694,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
]
@ -923,15 +727,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
@ -959,16 +754,10 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
dependencies = [
"crypto-mac 0.11.1",
"digest 0.9.0",
"crypto-mac",
"digest",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "ident_case"
version = "1.0.1"
@ -982,19 +771,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [
"autocfg",
"hashbrown 0.11.2",
"hashbrown",
"serde",
]
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
@ -1007,7 +787,7 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"ecdsa",
"elliptic-curve",
"sha2",
@ -1043,7 +823,7 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"winapi",
]
@ -1053,7 +833,7 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@ -1092,14 +872,6 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memmap2"
version = "0.1.0"
source = "git+https://github.com/certusone/wormhole#dac68d093c96c20f86d5b897ffc4667a34478111"
dependencies = [
"libc",
]
[[package]]
name = "memmap2"
version = "0.2.3"
@ -1162,26 +934,6 @@ dependencies = [
"wormhole-bridge-terra",
]
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
@ -1246,15 +998,6 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -1308,39 +1051,6 @@ dependencies = [
"syn",
]
[[package]]
name = "pyth-bridge"
version = "0.1.0"
dependencies = [
"bigint",
"cosmwasm-std",
"cosmwasm-storage",
"cosmwasm-vm",
"cw20",
"cw20-base",
"cw20-wrapped",
"generic-array 0.14.4",
"hex",
"k256",
"lazy_static",
"pyth-client",
"schemars",
"serde",
"serde_derive",
"serde_json",
"sha3",
"solana-program",
"terraswap",
"thiserror",
"wormhole-bridge-terra",
]
[[package]]
name = "pyth-client"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44de48029c54ec1ca570786b5baeb906b0fc2409c8e0145585e287ee7a526c72"
[[package]]
name = "quote"
version = "1.0.10"
@ -1350,19 +1060,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
]
[[package]]
name = "rand"
version = "0.8.4"
@ -1370,19 +1067,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_chacha",
"rand_core 0.6.3",
"rand_hc 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
"rand_hc",
]
[[package]]
@ -1413,15 +1100,6 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
@ -1476,23 +1154,6 @@ dependencies = [
"smallvec",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "region"
version = "2.2.0"
@ -1549,15 +1210,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.5"
@ -1606,21 +1258,6 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.130"
@ -1688,9 +1325,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cfg-if",
"cpufeatures",
"digest 0.9.0",
"digest",
"opaque-debug",
]
@ -1701,7 +1338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
dependencies = [
"block-buffer",
"digest 0.9.0",
"digest",
"keccak",
"opaque-debug",
]
@ -1712,7 +1349,7 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4"
dependencies = [
"digest 0.9.0",
"digest",
"rand_core 0.6.3",
]
@ -1722,96 +1359,6 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "solana-frozen-abi"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b81e60d88b1fe0322bba6f3fe6b0d7299df2f2ededa8d95ec77b934fabb967b"
dependencies = [
"bs58",
"bv",
"generic-array 0.14.4",
"log",
"memmap2 0.1.0",
"rustc_version",
"serde",
"serde_derive",
"sha2",
"solana-frozen-abi-macro",
"solana-logger",
"thiserror",
]
[[package]]
name = "solana-frozen-abi-macro"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f617daa0187bcc4665d63fcf9454c998e9cdad6a33181f6214558d738230bfe2"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "solana-logger"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b3e2b14bdcbb7b41de9ef5a541ac501ba3fbd07999cbcf7ea9006b3ae28b67b"
dependencies = [
"env_logger",
"lazy_static",
"log",
]
[[package]]
name = "solana-program"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c5d59f9d358c09db6461fae1fde6075a456685d856c004ef21af092a830e4e7"
dependencies = [
"bincode",
"blake3",
"borsh",
"borsh-derive",
"bs58",
"bv",
"curve25519-dalek 2.1.3",
"hex",
"itertools",
"lazy_static",
"log",
"num-derive",
"num-traits",
"rand 0.7.3",
"rustc_version",
"rustversion",
"serde",
"serde_bytes",
"serde_derive",
"sha2",
"sha3",
"solana-frozen-abi",
"solana-frozen-abi-macro",
"solana-logger",
"solana-sdk-macro",
"thiserror",
]
[[package]]
name = "solana-sdk-macro"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d27426b2a09676929c5e49df96967bbcffff003183c11a3c3ef11d78bac4aaaa"
dependencies = [
"bs58",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "spki"
version = "0.4.1"
@ -1868,23 +1415,14 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"rand 0.8.4",
"rand",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "terra-cosmwasm"
version = "2.2.0"
@ -1941,7 +1479,7 @@ dependencies = [
"cw20",
"cw20-base",
"cw20-wrapped",
"generic-array 0.14.4",
"generic-array",
"hex",
"k256",
"lazy_static",
@ -1954,22 +1492,13 @@ dependencies = [
"wormhole-bridge-terra",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "tracing"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@ -2043,7 +1572,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f52e455a01d0fac439cd7a96ba9b519bdc84e923a5b96034054697ebb17cd75"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"indexmap",
"loupe",
"more-asserts",
@ -2158,7 +1687,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aa390d123ebe23d5315c39f6063fcc18319661d03c8000f23d0fe1c011e8135"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"leb128",
"libloading",
"loupe",
@ -2180,7 +1709,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dffe8015f08915eb4939ebc8e521cde8246f272f5197ea60d46214ac5aef285"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"leb128",
"loupe",
"region",
@ -2237,7 +1766,7 @@ checksum = "469a12346a4831e7dac639b9646d8c9b24c7d2cf0cf458b77f489edb35060c1f"
dependencies = [
"backtrace",
"cc",
"cfg-if 1.0.0",
"cfg-if",
"indexmap",
"libc",
"loupe",
@ -2284,15 +1813,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@ -2309,7 +1829,7 @@ dependencies = [
"cw20",
"cw20-base",
"cw20-wrapped",
"generic-array 0.14.4",
"generic-array",
"getrandom 0.2.3",
"hex",
"k256",
@ -2326,3 +1846,8 @@ name = "zeroize"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
[[patch.unused]]
name = "memmap2"
version = "0.1.0"
source = "git+https://github.com/certusone/wormhole#dac68d093c96c20f86d5b897ffc4667a34478111"

View File

@ -1,5 +1,5 @@
[workspace]
members = ["contracts/cw20-wrapped", "contracts/wormhole", "contracts/token-bridge", "contracts/pyth-bridge", "contracts/nft-bridge", "contracts/cw721-wrapped", "packages/cw721", "contracts/cw721-base"]
members = ["contracts/cw20-wrapped", "contracts/wormhole", "contracts/token-bridge", "contracts/nft-bridge", "contracts/cw721-wrapped", "packages/cw721", "contracts/cw721-base"]
[profile.release]
opt-level = 3

View File

@ -1,39 +0,0 @@
[package]
name = "pyth-bridge"
version = "0.1.0"
authors = ["Wormhole Contributors <contact@certus.one>"]
edition = "2018"
description = "Pyth price receiver"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all init/handle/query exports
library = []
[dependencies]
cosmwasm-std = { version = "0.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
serde_derive = { version = "1.0.103"}
cw20 = "0.8.0"
cw20-base = { version = "0.8.0", features = ["library"] }
cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] }
terraswap = "2.4.0"
wormhole-bridge-terra = { path = "../wormhole", features = ["library"] }
thiserror = { version = "1.0.20" }
k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] }
sha3 = { version = "0.9.1", default-features = false }
generic-array = { version = "0.14.4" }
hex = "0.4.2"
lazy_static = "1.4.0"
bigint = "4"
pyth-client = "0.2.2"
solana-program = "=1.7.0"
[dev-dependencies]
cosmwasm-vm = { version = "0.16.0", default-features = false }
serde_json = "1.0"

View File

@ -1,192 +0,0 @@
use cosmwasm_std::{
entry_point,
to_binary,
Binary,
CosmosMsg,
Deps,
DepsMut,
Env,
MessageInfo,
QueryRequest,
Response,
StdError,
StdResult,
WasmMsg,
WasmQuery,
};
use crate::{
msg::{
ExecuteMsg,
InstantiateMsg,
MigrateMsg,
QueryMsg,
},
state::{
config,
config_read,
price_info,
price_info_read,
sequence,
sequence_read,
ConfigInfo,
UpgradeContract,
},
types::PriceAttestation,
};
use wormhole::{
byte_utils::get_string_from_32,
error::ContractError,
msg::QueryMsg as WormholeQueryMsg,
state::{
vaa_archive_add,
vaa_archive_check,
GovernancePacket,
ParsedVAA,
},
};
// Chain ID of Terra
const CHAIN_ID: u16 = 3;
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::new())
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
// Save general wormhole info
let state = ConfigInfo {
gov_chain: msg.gov_chain,
gov_address: msg.gov_address.as_slice().to_vec(),
wormhole_contract: msg.wormhole_contract,
pyth_emitter: msg.pyth_emitter.as_slice().to_vec(),
pyth_emitter_chain: msg.pyth_emitter_chain,
};
config(deps.storage).save(&state)?;
sequence(deps.storage).save(&0)?;
Ok(Response::default())
}
pub fn parse_vaa(deps: DepsMut, block_time: u64, data: &Binary) -> StdResult<ParsedVAA> {
let cfg = config_read(deps.storage).load()?;
let vaa: ParsedVAA = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: cfg.wormhole_contract.clone(),
msg: to_binary(&WormholeQueryMsg::VerifyVAA {
vaa: data.clone(),
block_time,
})?,
}))?;
Ok(vaa)
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data),
}
}
fn submit_vaa(
mut deps: DepsMut,
env: Env,
_info: MessageInfo,
data: &Binary,
) -> StdResult<Response> {
let state = config_read(deps.storage).load()?;
let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), data)?;
let data = vaa.payload;
// check if vaa is from governance
if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
if vaa_archive_check(deps.storage, vaa.hash.as_slice()) {
return ContractError::VaaAlreadyExecuted.std_err();
}
vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
return handle_governance_payload(deps, env, &data);
}
// IMPORTANT: VAA replay-protection is not implemented in this code-path
// Sequences are used to prevent replay or price rollbacks
let message =
PriceAttestation::deserialize(&data[..]).map_err(|_| ContractError::InvalidVAA.std())?;
if vaa.emitter_address != state.pyth_emitter || vaa.emitter_chain != state.pyth_emitter_chain {
return ContractError::InvalidVAA.std_err();
}
// Check sequence
let last_sequence = sequence_read(deps.storage).load()?;
if vaa.sequence <= last_sequence && last_sequence != 0 {
return Err(StdError::generic_err(
"price sequences need to be monotonically increasing",
));
}
sequence(deps.storage).save(&vaa.sequence)?;
// Update price
price_info(deps.storage).save(&message.price_id.to_bytes()[..], &data)?;
Ok(Response::new()
.add_attribute("action", "price_update")
.add_attribute("price_feed", message.price_id.to_string()))
}
fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
let gov_packet = GovernancePacket::deserialize(&data)?;
let module = get_string_from_32(&gov_packet.module);
if module != "PythBridge" {
return Err(StdError::generic_err("this is not a valid module"));
}
if gov_packet.chain != 0 && gov_packet.chain != CHAIN_ID {
return Err(StdError::generic_err(
"the governance VAA is for another chain",
));
}
match gov_packet.action {
2u8 => handle_upgrade_contract(deps, env, &gov_packet.payload),
_ => ContractError::InvalidVAAAction.std_err(),
}
}
fn handle_upgrade_contract(_deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
let UpgradeContract { new_contract } = UpgradeContract::deserialize(&data)?;
Ok(Response::new()
.add_message(CosmosMsg::Wasm(WasmMsg::Migrate {
contract_addr: env.contract.address.to_string(),
new_code_id: new_contract,
msg: to_binary(&MigrateMsg {})?,
}))
.add_attribute("action", "contract_upgrade"))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::PriceInfo { price_id } => {
to_binary(&query_price_info(deps, price_id.as_slice())?)
}
}
}
pub fn query_price_info(deps: Deps, address: &[u8]) -> StdResult<PriceAttestation> {
match price_info_read(deps.storage).load(address) {
Ok(data) => PriceAttestation::deserialize(&data[..]).map_err(|_| {
StdError::parse_err("PriceAttestation", "failed to decode price attestation")
}),
Err(_) => ContractError::AssetNotFound.std_err(),
}
}

View File

@ -1,7 +0,0 @@
#[cfg(test)]
extern crate lazy_static;
pub mod contract;
pub mod msg;
pub mod state;
pub mod types;

View File

@ -1,35 +0,0 @@
use cosmwasm_std::Binary;
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
// governance contract details
pub gov_chain: u16,
pub gov_address: Binary,
pub wormhole_contract: HumanAddr,
pub pyth_emitter: Binary,
pub pyth_emitter_chain: u16,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
SubmitVaa { data: Binary },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
PriceInfo { price_id: Binary },
}

View File

@ -1,76 +0,0 @@
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
use cosmwasm_std::{
StdResult,
Storage,
};
use cosmwasm_storage::{
bucket,
bucket_read,
singleton,
singleton_read,
Bucket,
ReadonlyBucket,
ReadonlySingleton,
Singleton,
};
use wormhole::byte_utils::ByteUtils;
type HumanAddr = String;
pub static CONFIG_KEY: &[u8] = b"config";
pub static PRICE_INFO_KEY: &[u8] = b"price_info";
pub static SEQUENCE_KEY: &[u8] = b"sequence";
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ConfigInfo {
// governance contract details
pub gov_chain: u16,
pub gov_address: Vec<u8>,
pub wormhole_contract: HumanAddr,
pub pyth_emitter: Vec<u8>,
pub pyth_emitter_chain: u16,
}
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
singleton_read(storage, CONFIG_KEY)
}
pub fn sequence(storage: &mut dyn Storage) -> Singleton<u64> {
singleton(storage, SEQUENCE_KEY)
}
pub fn sequence_read(storage: &dyn Storage) -> ReadonlySingleton<u64> {
singleton_read(storage, SEQUENCE_KEY)
}
pub fn price_info(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
bucket(storage, PRICE_INFO_KEY)
}
pub fn price_info_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
bucket_read(storage, PRICE_INFO_KEY)
}
pub struct UpgradeContract {
pub new_contract: u64,
}
impl UpgradeContract {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let new_contract = data.get_u64(24);
Ok(UpgradeContract { new_contract })
}
}

View File

@ -1,235 +0,0 @@
pub mod pyth_extensions;
use std::{
convert::TryInto,
io::Read,
mem,
};
use solana_program::{
clock::UnixTimestamp,
pubkey::Pubkey,
};
use self::pyth_extensions::{
P2WCorpAction,
P2WEma,
P2WPriceStatus,
P2WPriceType,
};
// Constants and values common to every p2w custom-serialized message
/// Precedes every message implementing the p2w serialization format
pub const P2W_MAGIC: &'static [u8] = b"P2WH";
/// Format version used and understood by this codebase
pub const P2W_FORMAT_VERSION: u16 = 1;
pub const PUBKEY_LEN: usize = 32;
/// Decides the format of following bytes
#[repr(u8)]
pub enum PayloadId {
PriceAttestation = 1,
}
// On-chain data types
#[derive(
Clone, Default, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize,
)]
pub struct PriceAttestation {
pub product_id: Pubkey,
pub price_id: Pubkey,
pub price_type: P2WPriceType,
pub price: i64,
pub expo: i32,
pub twap: P2WEma,
pub twac: P2WEma,
pub confidence_interval: u64,
pub status: P2WPriceStatus,
pub corp_act: P2WCorpAction,
pub timestamp: UnixTimestamp,
}
impl PriceAttestation {
/// Serialize this attestation according to the Pyth-over-wormhole serialization format
pub fn serialize(&self) -> Vec<u8> {
// A nifty trick to get us yelled at if we forget to serialize a field
#[deny(warnings)]
let PriceAttestation {
product_id,
price_id,
price_type,
price,
expo,
twap,
twac,
confidence_interval,
status,
corp_act,
timestamp,
} = self;
// magic
let mut buf = P2W_MAGIC.to_vec();
// version
buf.extend_from_slice(&P2W_FORMAT_VERSION.to_be_bytes()[..]);
// payload_id
buf.push(PayloadId::PriceAttestation as u8);
// product_id
buf.extend_from_slice(&product_id.to_bytes()[..]);
// price_id
buf.extend_from_slice(&price_id.to_bytes()[..]);
// price_type
buf.push(price_type.clone() as u8);
// price
buf.extend_from_slice(&price.to_be_bytes()[..]);
// exponent
buf.extend_from_slice(&expo.to_be_bytes()[..]);
// twap
buf.append(&mut twap.serialize());
// twac
buf.append(&mut twac.serialize());
// confidence_interval
buf.extend_from_slice(&confidence_interval.to_be_bytes()[..]);
// status
buf.push(status.clone() as u8);
// corp_act
buf.push(corp_act.clone() as u8);
// timestamp
buf.extend_from_slice(&timestamp.to_be_bytes()[..]);
buf
}
pub fn deserialize(mut bytes: impl Read) -> Result<Self, Box<dyn std::error::Error>> {
use P2WCorpAction::*;
use P2WPriceStatus::*;
use P2WPriceType::*;
println!("Using {} bytes for magic", P2W_MAGIC.len());
let mut magic_vec = vec![0u8; P2W_MAGIC.len()];
bytes.read_exact(magic_vec.as_mut_slice())?;
if magic_vec.as_slice() != P2W_MAGIC {
return Err(format!(
"Invalid magic {:02X?}, expected {:02X?}",
magic_vec, P2W_MAGIC,
)
.into());
}
let mut version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VERSION)];
bytes.read_exact(version_vec.as_mut_slice())?;
let version = u16::from_be_bytes(version_vec.as_slice().try_into()?);
if version != P2W_FORMAT_VERSION {
return Err(format!(
"Unsupported format version {}, expected {}",
version, P2W_FORMAT_VERSION
)
.into());
}
let mut payload_id_vec = vec![0u8; mem::size_of::<PayloadId>()];
bytes.read_exact(payload_id_vec.as_mut_slice())?;
if PayloadId::PriceAttestation as u8 != payload_id_vec[0] {
return Err(format!(
"Invalid Payload ID {}, expected {}",
payload_id_vec[0],
PayloadId::PriceAttestation as u8,
)
.into());
}
let mut product_id_vec = vec![0u8; PUBKEY_LEN];
bytes.read_exact(product_id_vec.as_mut_slice())?;
let product_id = Pubkey::new(product_id_vec.as_slice());
let mut price_id_vec = vec![0u8; PUBKEY_LEN];
bytes.read_exact(price_id_vec.as_mut_slice())?;
let price_id = Pubkey::new(price_id_vec.as_slice());
let mut price_type_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
bytes.read_exact(price_type_vec.as_mut_slice())?;
let price_type = match price_type_vec[0] {
a if a == Price as u8 => Price,
a if a == P2WPriceType::Unknown as u8 => P2WPriceType::Unknown,
other => {
return Err(format!("Invalid price_type value {}", other).into());
}
};
let mut price_vec = vec![0u8; mem::size_of::<i64>()];
bytes.read_exact(price_vec.as_mut_slice())?;
let price = i64::from_be_bytes(price_vec.as_slice().try_into()?);
let mut expo_vec = vec![0u8; mem::size_of::<i32>()];
bytes.read_exact(expo_vec.as_mut_slice())?;
let expo = i32::from_be_bytes(expo_vec.as_slice().try_into()?);
let twap = P2WEma::deserialize(&mut bytes)?;
let twac = P2WEma::deserialize(&mut bytes)?;
println!("twac OK");
let mut confidence_interval_vec = vec![0u8; mem::size_of::<u64>()];
bytes.read_exact(confidence_interval_vec.as_mut_slice())?;
let confidence_interval =
u64::from_be_bytes(confidence_interval_vec.as_slice().try_into()?);
let mut status_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
bytes.read_exact(status_vec.as_mut_slice())?;
let status = match status_vec[0] {
a if a == P2WPriceStatus::Unknown as u8 => P2WPriceStatus::Unknown,
a if a == Trading as u8 => Trading,
a if a == Halted as u8 => Halted,
a if a == Auction as u8 => Auction,
other => {
return Err(format!("Invalid status value {}", other).into());
}
};
let mut corp_act_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
bytes.read_exact(corp_act_vec.as_mut_slice())?;
let corp_act = match corp_act_vec[0] {
a if a == NoCorpAct as u8 => NoCorpAct,
other => {
return Err(format!("Invalid corp_act value {}", other).into());
}
};
let mut timestamp_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
bytes.read_exact(timestamp_vec.as_mut_slice())?;
let timestamp = UnixTimestamp::from_be_bytes(timestamp_vec.as_slice().try_into()?);
Ok(Self {
product_id,
price_id,
price_type,
price,
expo,
twap,
twac,
confidence_interval,
status,
corp_act,
timestamp,
})
}
}

View File

@ -1,161 +0,0 @@
//! This module contains 1:1 (or close) copies of selected Pyth types
//! with quick and dirty enhancements.
use std::{
convert::TryInto,
io::Read,
mem,
};
use pyth_client::{
CorpAction,
Ema,
PriceStatus,
PriceType,
};
/// 1:1 Copy of pyth_client::PriceType with derived additional traits.
#[derive(Clone, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
#[repr(u8)]
pub enum P2WPriceType {
Unknown,
Price,
}
impl From<&PriceType> for P2WPriceType {
fn from(pt: &PriceType) -> Self {
match pt {
PriceType::Unknown => Self::Unknown,
PriceType::Price => Self::Price,
}
}
}
impl Default for P2WPriceType {
fn default() -> Self {
Self::Price
}
}
/// 1:1 Copy of pyth_client::PriceStatus with derived additional traits.
#[derive(Clone, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
pub enum P2WPriceStatus {
Unknown,
Trading,
Halted,
Auction,
}
impl From<&PriceStatus> for P2WPriceStatus {
fn from(ps: &PriceStatus) -> Self {
match ps {
PriceStatus::Unknown => Self::Unknown,
PriceStatus::Trading => Self::Trading,
PriceStatus::Halted => Self::Halted,
PriceStatus::Auction => Self::Auction,
}
}
}
impl Default for P2WPriceStatus {
fn default() -> Self {
Self::Trading
}
}
/// 1:1 Copy of pyth_client::CorpAction with derived additional traits.
#[derive(Clone, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
pub enum P2WCorpAction {
NoCorpAct,
}
impl Default for P2WCorpAction {
fn default() -> Self {
Self::NoCorpAct
}
}
impl From<&CorpAction> for P2WCorpAction {
fn from(ca: &CorpAction) -> Self {
match ca {
CorpAction::NoCorpAct => P2WCorpAction::NoCorpAct,
}
}
}
/// 1:1 Copy of pyth_client::Ema with all-pub fields.
#[derive(
Clone, Default, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize,
)]
#[repr(C)]
pub struct P2WEma {
pub val: i64,
pub numer: i64,
pub denom: i64,
}
/// CAUTION: This impl may panic and requires an unsafe cast
impl From<&Ema> for P2WEma {
fn from(ema: &Ema) -> Self {
let our_size = mem::size_of::<P2WEma>();
let upstream_size = mem::size_of::<Ema>();
if our_size == upstream_size {
unsafe { std::mem::transmute_copy(ema) }
} else {
dbg!(our_size);
dbg!(upstream_size);
// Because of private upstream fields it's impossible to
// complain about type-level changes at compile-time
panic!("P2WEma sizeof mismatch")
}
}
}
/// CAUTION: This impl may panic and requires an unsafe cast
impl Into<Ema> for &P2WEma {
fn into(self) -> Ema {
let our_size = mem::size_of::<P2WEma>();
let upstream_size = mem::size_of::<Ema>();
if our_size == upstream_size {
unsafe { std::mem::transmute_copy(self) }
} else {
dbg!(our_size);
dbg!(upstream_size);
// Because of private upstream fields it's impossible to
// complain about type-level changes at compile-time
panic!("P2WEma sizeof mismatch")
}
}
}
impl P2WEma {
pub fn serialize(&self) -> Vec<u8> {
let mut v = vec![];
// val
v.extend(&self.val.to_be_bytes()[..]);
// numer
v.extend(&self.numer.to_be_bytes()[..]);
// denom
v.extend(&self.denom.to_be_bytes()[..]);
v
}
pub fn deserialize(mut bytes: impl Read) -> Result<Self, Box<dyn std::error::Error>> {
let mut val_vec = vec![0u8; mem::size_of::<i64>()];
bytes.read_exact(val_vec.as_mut_slice())?;
let val = i64::from_be_bytes(val_vec.as_slice().try_into()?);
let mut numer_vec = vec![0u8; mem::size_of::<i64>()];
bytes.read_exact(numer_vec.as_mut_slice())?;
let numer = i64::from_be_bytes(numer_vec.as_slice().try_into()?);
let mut denom_vec = vec![0u8; mem::size_of::<i64>()];
bytes.read_exact(denom_vec.as_mut_slice())?;
let denom = i64::from_be_bytes(denom_vec.as_slice().try_into()?);
Ok(Self { val, numer, denom })
}
}

View File

@ -19,7 +19,6 @@ const artifacts = [
"token_bridge.wasm",
"cw20_wrapped.wasm",
"cw20_base.wasm",
"pyth_bridge.wasm",
"nft_bridge.wasm",
"cw721_wrapped.wasm",
"cw721_base.wasm",
@ -134,7 +133,11 @@ async function instantiate(contract, inst_msg) {
.then((rs) => {
address = /"contract_address","value":"([^"]+)/gm.exec(rs.raw_log)[1];
});
console.log(`Instantiated ${contract} at ${address} (${convert_terra_address_to_hex(address)})`);
console.log(
`Instantiated ${contract} at ${address} (${convert_terra_address_to_hex(
address
)})`
);
return address;
}
@ -180,20 +183,6 @@ addresses["mock.wasm"] = await instantiate("cw20_base.wasm", {
mint: null,
});
const pythEmitterAddress =
"71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b";
const pythChain = 1;
addresses["pyth_bridge.wasm"] = await instantiate("pyth_bridge.wasm", {
gov_chain: govChain,
gov_address: Buffer.from(govAddress, "hex").toString("base64"),
wormhole_contract: addresses["wormhole.wasm"],
pyth_emitter: Buffer.from(pythEmitterAddress, "hex").toString(
"base64"
),
pyth_emitter_chain: pythChain,
});
addresses["nft_bridge.wasm"] = await instantiate("nft_bridge.wasm", {
gov_chain: govChain,
gov_address: Buffer.from(govAddress, "hex").toString("base64"),
@ -230,12 +219,19 @@ async function mint_cw721(token_id, token_uri) {
}),
})
.then((tx) => terra.tx.broadcast(tx));
console.log(`Minted NFT with token_id ${token_id} at ${addresses["cw721_base.wasm"]}`);
console.log(
`Minted NFT with token_id ${token_id} at ${addresses["cw721_base.wasm"]}`
);
}
await mint_cw721(0, 'https://ixmfkhnh2o4keek2457f2v2iw47cugsx23eynlcfpstxihsay7nq.arweave.net/RdhVHafTuKIRWud-XVdItz4qGlfWyYasRXyndB5Ax9s/');
await mint_cw721(1, 'https://portal.neondistrict.io/api/getNft/158456327500392944014123206890');
await mint_cw721(
0,
"https://ixmfkhnh2o4keek2457f2v2iw47cugsx23eynlcfpstxihsay7nq.arweave.net/RdhVHafTuKIRWud-XVdItz4qGlfWyYasRXyndB5Ax9s/"
);
await mint_cw721(
1,
"https://portal.neondistrict.io/api/getNft/158456327500392944014123206890"
);
/* Registrations: tell the bridge contracts to know about each other */
@ -285,7 +281,6 @@ for (const [contract, registrations] of Object.entries(
}
}
// Terra addresses are "human-readable", but for cross-chain registrations, we
// want the "canonical" version
function convert_terra_address_to_hex(human_addr) {

View File

@ -1,19 +0,0 @@
FROM bridge-client
RUN apt-get install -y python3
ADD third_party/pyth/pyth_utils.py /usr/src/pyth/pyth_utils.py
ADD third_party/pyth/p2w_autoattest.py /usr/src/pyth/p2w_autoattest.py
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=target \
--mount=type=cache,target=pyth2wormhole/target \
cargo build --manifest-path ./pyth2wormhole/Cargo.toml --package pyth2wormhole-client && \
mv pyth2wormhole/target/debug/pyth2wormhole-client /usr/local/bin/pyth2wormhole-client && \
chmod a+rx /usr/src/pyth/*.py
ENV P2W_OWNER_KEYPAIR="/usr/src/solana/keys/p2w_owner.json"
ENV P2W_ATTESTATIONS_PORT="4343"
ENV PYTH_PUBLISHER_KEYPAIR="/usr/src/solana/keys/pyth_publisher.json"
ENV PYTH_PROGRAM_KEYPAIR="/usr/src/solana/keys/pyth_program.json"
ENV SOL_AIRDROP_AMT="100"

View File

@ -1,47 +0,0 @@
# syntax=docker/dockerfile:1.2
# Wormhole-specific setup for pyth
FROM pythfoundation/pyth-client:devnet-v2.2@sha256:2ce8de6a43b2fafafd15ebdb723c530a0319860dc40c9fdb97149d5aa270fdde
USER root
# At the time of this writing, debian is fussy about performing an
# apt-get update. Please add one if repos go stale
RUN apt-get install -y netcat-openbsd python3 && \
rm -rf /var/lib/apt/lists/*
ADD solana/keys /opt/solana/keys
ENV PYTH_KEY_STORE=/home/pyth/.pythd
# Prepare keys
WORKDIR $PYTH_KEY_STORE
RUN cp /opt/solana/keys/pyth_publisher.json publish_key_pair.json && \
cp /opt/solana/keys/pyth_program.json program_key_pair.json && \
chown pyth:pyth -R . && \
chmod go-rwx -R .
# Rebuild Pyth sources from scratch
# This comes soon after mainnet-v2.1
ENV PYTH_SRC_REV=31e3188bbf52ec1a25f71e4ab969378b27415b0a
ENV PYTH_SRC_ROOT=/home/pyth/pyth-client
ADD https://github.com/pyth-network/pyth-client/archive/$PYTH_SRC_REV.tar.gz .
RUN tar -xvf *.tar.gz && \
rm -rf $PYTH_SRC_ROOT *.tar.gz && \
mv pyth-client-$PYTH_SRC_REV $PYTH_SRC_ROOT/
WORKDIR $PYTH_SRC_ROOT/build
RUN cmake .. && make
# Prepare setup script
ADD third_party/pyth/pyth_utils.py /opt/pyth/pyth_utils.py
ADD third_party/pyth/pyth_publisher.py /opt/pyth/pyth_publisher.py
RUN chmod a+rx /opt/pyth/*.py
USER pyth
ENV PYTH=$PYTH_SRC_ROOT/build/pyth
ENV READINESS_PORT=2000
ENV SOL_AIRDROP_AMT=100

View File

@ -1,33 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# ethereum contracts
/contracts
/src/*-contracts/
# tsproto output
/src/proto
# build
/lib

View File

@ -1,40 +0,0 @@
FROM node:16-alpine@sha256:004dbac84fed48e20f9888a23e32fa7cf83c2995e174a78d41d9a9dd1e051a20
# npm needs a Python for some of the deps
RUN apk add git python3 make build-base
# Build ETH
WORKDIR /usr/src/ethereum
ADD ethereum .
RUN --mount=type=cache,target=/home/node/.npm \
npm ci
# Build Wormhole SDK
WORKDIR /usr/src/sdk/js
ADD sdk/js/ .
RUN --mount=type=cache,target=/home/node/.npm \
npm ci && npm run build
# Build p2w-sdk in dir preserving directory structure
WORKDIR /usr/src/third_party/pyth/p2w-sdk
COPY third_party/pyth/p2w-sdk/package.json third_party/pyth/p2w-sdk/package-lock.json .
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.npm \
npm ci
COPY third_party/pyth/p2w-sdk .
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.npm \
npm run build
# Build p2w-relay
WORKDIR /usr/src/third_party/pyth/p2w-relay
COPY third_party/pyth/p2w-relay/package.json third_party/pyth/p2w-relay/package-lock.json .
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.npm \
npm ci
COPY third_party/pyth/p2w-relay .
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.npm \
npm run build

View File

@ -1,24 +0,0 @@
# Pyth2wormhole relay example
IMPORTANT: This is not ready for production.
This package is an example Pyth2wormhole relayer implementation. The
main focus is to provide an automated integration test that will
perform last-mile delivery of Pyth2wormhole price attestations.
# How it works
## Relayer recap
When attesting with Wormhole, the final step consists of a query for
the guardian-signed attestation data on the guardian public RPC,
followed by posting the data to each desired target chain
contract. Each target chain contract lets callers verify the payload's
signatures, thus proving its validity. This activity means being
a Wormhole **relayer**.
## How this package relays attestations
`p2w-relay` is a Node.js relayer script targeting ETH that will
periodically query its source-chain counterpart for new sequence
numbers to query from the guardians. Any pending sequence numbers will
stick around in a global state until their corresponding messages are
successfully retrieved from the guardians. Later, target chain calls
are made and a given seqno is deleted from the pool. Failed target
chain calls will not be retried.

File diff suppressed because it is too large Load Diff

View File

@ -1,53 +0,0 @@
{
"name": "@certusone/p2w-relay",
"version": "0.1.0",
"description": "p2w-sdk integration test; not intended for production use",
"private": true,
"types": "lib/index.d.ts",
"main": "lib/index.js",
"files": [
"lib/**/*"
],
"scripts": {
"start": "node -r esm lib/index.js",
"build": "npm run build-eth-types && npm run build-lib",
"build-lib": "npm run copy-artifacts && tsc",
"build-watch": "npm run build-eth-types && npm run copy-artifacts && tsc --watch",
"build-eth-types": "node scripts/copyEthContracts.cjs && typechain --target=ethers-v5 --out-dir=src/ethers-contracts contracts/*.json",
"copy-artifacts": "node scripts/copyWasm.cjs && node scripts/copyEthersTypes.cjs",
"lint": "tslint -p tsconfig.json",
"postversion": "git push && git push --tags",
"preversion": "npm run lint",
"version": "npm run format && git add -A src"
},
"repository": {
"type": "git",
"url": "git+https://github.com/certusone/wormhole.git"
},
"author": "https://certus.one",
"license": "MIT",
"devDependencies": {
"@openzeppelin/contracts": "^4.2.0",
"@typechain/ethers-v5": "^7.1.2",
"@types/long": "^4.0.1",
"@types/node": "^16.6.1",
"copy-dir": "^1.3.0",
"esm": "^3.2.25",
"ethers": "^5.4.7",
"find": "^0.3.0",
"prettier": "^2.3.2",
"ts-loader": "^9.2.5",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.3.5"
},
"dependencies": {
"@certusone/p2w-sdk": "file:../p2w-sdk",
"@certusone/wormhole-sdk": "file:../../../sdk/js",
"@improbable-eng/grpc-web-node-http-transport": "^0.14.1"
},
"bugs": {
"url": "https://github.com/certusone/wormhole/issues"
},
"homepage": "https://github.com/certusone/wormhole#readme"
}

View File

@ -1,2 +0,0 @@
const copydir = require("copy-dir");
copydir.sync("../../../ethereum/build/contracts", "./contracts");

View File

@ -1,17 +0,0 @@
const find = require("find");
const fs = require("fs");
const path = require("path");
const SOURCE_ROOT = "src";
const TARGET_ROOT = "lib";
find.eachfile(/\.d\.ts(\..*)?/, SOURCE_ROOT, fname => {
fname_copy = fname.replace(SOURCE_ROOT, TARGET_ROOT);
console.log("copying types:", fname, "to", fname_copy);
fs.mkdirSync(path.dirname(fname_copy), {recursive: true});
fs.copyFileSync(fname, fname_copy);
});

View File

@ -1,17 +0,0 @@
const find = require("find");
const fs = require("fs");
const path = require("path");
const SOURCE_ROOT = "src";
const TARGET_ROOT = "lib";
find.eachfile(/\.wasm(\..*)?/, SOURCE_ROOT, fname => {
fname_copy = fname.replace(SOURCE_ROOT, TARGET_ROOT);
console.log("copyWasm:", fname, "to", fname_copy);
fs.mkdirSync(path.dirname(fname_copy), {recursive: true});
fs.copyFileSync(fname, fname_copy);
});

View File

@ -1,198 +0,0 @@
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
import {PythImplementation__factory} from "./ethers-contracts";
import * as http from "http";
import * as net from "net";
import fs from "fs";
import {ethers} from "ethers";
import {getSignedAttestation, parseBatchAttestation, p2w_core, sol_addr2buf} from "@certusone/p2w-sdk";
import {setDefaultWasm, importCoreWasm} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
interface NewAttestationsResponse {
pendingSeqnos: Array<number>,
}
async function readinessProbeRoutine(port: number) {
let srv = net.createServer();
return await srv.listen(port);
}
(async () => {
// p2w-attest exposes an HTTP endpoint that shares the currently pending sequence numbers
const P2W_ATTESTATIONS_HOST = process.env.P2W_ATTESTATIONS_HOST || "p2w-attest";
const P2W_ATTESTATIONS_PORT = Number(process.env.P2W_ATTESTATIONS_PORT || "4343");
const P2W_ATTESTATIONS_POLL_INTERVAL_MS = Number(process.env.P2W_ATTESTATIONS_POLL_INTERVAL_MS || "5000");
const P2W_SOL_ADDRESS = process.env.P2W_SOL_ADDRESS || "P2WH424242424242424242424242424242424242424";
const READINESS_PROBE_PORT = Number(process.env.READINESS_PROBE_PORT || "2000");
const P2W_RELAY_RETRY_COUNT = Number(process.env.P2W_RELAY_RETRY_COUNT || "3");
// ETH node connection details; Currently, we expect to read BIP44
// wallet recovery mnemonics from a text file.
const ETH_NODE_URL = process.env.ETH_NODE_URL || "ws://eth-devnet:8545";
const ETH_P2W_CONTRACT = process.env.ETH_P2W_CONTRACT || "0xA94B7f0465E98609391C623d0560C5720a3f2D33";
const ETH_MNEMONIC_FILE = process.env.ETH_MNEMONIC_FILE || "../../../ethereum/devnet_mnemonic.txt";
const ETH_HD_WALLET_PATH = process.env.ETH_HD_WALLET_PATH || "m/44'/60'/0'/0/0";
// Public RPC address for use with signed attestation queries
const GUARDIAN_RPC_HOST_PORT = process.env.GUARDIAN_RPC_HOST_PORT || "http://guardian:7071";
let readinessProbe = null;
let seqnoPool: Map<number, number> = new Map();
console.log(`Polling attestations endpoint every ${P2W_ATTESTATIONS_POLL_INTERVAL_MS / 1000} seconds`);
setDefaultWasm("node");
const {parse_vaa} = await importCoreWasm();
let p2w_eth: any;
// Connect to ETH
try {
let provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
let mnemonic: string = fs.readFileSync(ETH_MNEMONIC_FILE).toString("utf-8").trim();
let wallet = ethers.Wallet.fromMnemonic(mnemonic, ETH_HD_WALLET_PATH);
console.log(`Using ETH wallet pubkey: ${wallet.publicKey}`);
let signer = new ethers.Wallet(wallet.privateKey, provider);
let balance = await signer.getBalance();
console.log(`Account balance is ${balance}`);
let factory = new PythImplementation__factory(signer);
p2w_eth = factory.attach(ETH_P2W_CONTRACT);
}
catch(e) {
console.error(`Error: Could not instantiate ETH contract:`, e);
throw e;
}
while (true) {
http.get({
hostname: P2W_ATTESTATIONS_HOST,
port: P2W_ATTESTATIONS_PORT,
path: "/",
agent: false
}, (res) => {
if (res.statusCode != 200) {
console.error("Could not reach attestations endpoint", res);
} else {
let chunks: string[] = [];
res.setEncoding("utf-8");
res.on('data', (chunk) => {
chunks.push(chunk);
});
res.on('end', () => {
let body = chunks.join('');
let response: NewAttestationsResponse = JSON.parse(body);
console.log(`Got ${response.pendingSeqnos.length} new seqnos: ${response.pendingSeqnos}`);
for (let seqno of response.pendingSeqnos) {
seqnoPool.set(seqno, 0);
}
});
}
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
console.log("Processing seqnos:", seqnoPool);
for (let poolEntry of seqnoPool) {
let seqno = poolEntry[0];
let attempts = poolEntry[1];
if (attempts >= P2W_RELAY_RETRY_COUNT) {
console.warn(`[seqno ${poolEntry}] Exceeded retry count, removing from list`);
seqnoPool.delete(seqno);
continue;
}
let vaaResponse: any;
try {
vaaResponse = await getSignedAttestation(
GUARDIAN_RPC_HOST_PORT,
P2W_SOL_ADDRESS,
seqno,
{
transport: NodeHttpTransport()
}
);
}
catch(e) {
console.error(`[seqno ${poolEntry}] Error: Could not call getSignedAttestation:`, e);
seqnoPool.set(seqno, attempts + 1);
continue;
}
console.log(`[seqno ${poolEntry}] Price attestation VAA bytes:\n`, vaaResponse.vaaBytes);
let parsedVaa = parse_vaa(vaaResponse.vaaBytes);
console.log(`[seqno ${poolEntry}] Parsed VAA:\n`, parsedVaa);
let parsedAttestations = await parseBatchAttestation(parsedVaa.payload);
console.log(`[seqno ${poolEntry}] Parsed ${parsedAttestations.length} price attestations:\n`, parsedAttestations);
// try {
// let tx = await p2w_eth.attestPrice(vaaResponse.vaaBytes, {gasLimit: 1000000});
// let retval = await tx.wait();
// console.log(`[seqno ${poolEntry}] attestPrice() output:\n`, retval);
// } catch(e) {
// console.error(`[seqno ${poolEntry}, {parsedAttestations.length} symbols] Error: Could not call attestPrice() on ETH:`, e);
// seqnoPool.set(seqno, attempts + 1);
// continue;
// }
console.warn("TODO: implement relayer ETH call");
// for (let att of parsedAttestations) {
// let product_id = att.product_id;
// let price_type = att.price_type == "Price" ? 1 : 0;
// let latest_attestation: any;
// try {
// let p2w = await p2w_core();
// console.log(`Looking up latestAttestation for `, product_id, price_type);
// latest_attestation = await p2w_eth.latestAttestation(product_id, price_type);
// } catch(e) {
// console.error(`[seqno ${poolEntry}] Error: Could not call latestAttestation() on ETH:`, e);
// seqnoPool.set(seqno, attempts + 1);
// continue;
// }
// console.log(`[seqno ${poolEntry}] Latest price type ${price_type} attestation of ${product_id} is ${latest_attestation}`);
// }
if (!readinessProbe) {
console.log(`[seqno ${poolEntry}] Attestation successful. Starting readiness probe.`);
readinessProbe = readinessProbeRoutine(READINESS_PROBE_PORT);
}
seqnoPool.delete(seqno); // Everything went well, seqno no longer pending.
}
await new Promise(f => {setTimeout(f, P2W_ATTESTATIONS_POLL_INTERVAL_MS);});
}
})();

View File

@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"downlevelIteration": true,
"allowJs": true,
},
"include": ["src", "types"],
"exclude": ["node_modules", "**/__tests__/*"]
}

View File

@ -1,9 +0,0 @@
{
"extends": ["tslint:recommended", "tslint-config-prettier"],
"linterOptions": {
"exclude": [
"src/proto/**"
]
}
}

View File

@ -1,33 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# ethereum contracts
/contracts
/src/*-contracts/
# tsproto output
/src/proto
# build
/lib

View File

@ -1,23 +0,0 @@
FROM node:16-alpine@sha256:004dbac84fed48e20f9888a23e32fa7cf83c2995e174a78d41d9a9dd1e051a20
# Build ETH
WORKDIR /usr/src/ethereum
COPY ethereum/package.json ethereum/package-lock.json ./
RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \
npm ci
COPY ethereum .
# Build Wormhole SDK
WORKDIR /usr/src/sdk/js
COPY sdk/js/package.json sdk/js/package-lock.json ./
RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \
npm ci
COPY sdk/js .
# Build p2w-sdk in dir preserving directory structure
WORKDIR /usr/src/third_party/pyth/p2w-sdk
COPY third_party/pyth/p2w-sdk/package.json third_party/pyth/p2w-sdk/package-lock.json ./
RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \
npm ci
COPY third_party/pyth/p2w-sdk .
RUN npm run build-test

View File

@ -1,15 +0,0 @@
# Pyth2wormhole SDK
This project contains a library for interacting with pyth2wormhole and adjacent APIs.
# Install
For now, the in-house dependencies are referenced by relative
path. The commands below will build those. For an automated version of
this process, please refer to `p2w-relay`'s Dockerfile and/or our [Tilt](https://tilt.dev)
devnet with `pyth` enabled.
```shell
# Run the commands in this README's directory for --prefix to work
$ npm --prefix ../../../ethereum ci && npm --prefix ../../../ethereum run build # ETH contracts
$ npm --prefix ../../../sdk/js ci # Wormhole SDK
$ npm ci && npm run build # Pyth2wormhole SDK
```

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +0,0 @@
{
"name": "@certusone/p2w-sdk",
"version": "0.1.0",
"description": "TypeScript library for interacting with Pyth2Wormhole",
"types": "lib/index.d.ts",
"main": "lib/index.js",
"files": [
"lib/**/*"
],
"scripts": {
"build": "npm run build-eth-types && npm run build-lib",
"build-eth-types": "node scripts/copyEthContracts.cjs && typechain --target=ethers-v5 --out-dir=src/ethers-contracts contracts/*.json",
"build-lib": "npm run copy-artifacts && tsc",
"build-watch": "npm run copy-artifacts && tsc --watch",
"copy-artifacts": "node scripts/copyWasm.cjs && node scripts/copyEthersTypes.cjs",
"lint": "tslint -p tsconfig.json",
"postversion": "git push && git push --tags",
"preversion": "npm run lint",
"version": "npm run format && git add -A src"
},
"repository": {
"type": "git",
"url": "git+https://github.com/certusone/wormhole.git"
},
"author": "https://certus.one",
"license": "MIT",
"devDependencies": {
"@openzeppelin/contracts": "^4.2.0",
"@typechain/ethers-v5": "^7.1.2",
"@types/long": "^4.0.1",
"@types/node": "^16.6.1",
"copy-dir": "^1.3.0",
"find": "^0.3.0",
"prettier": "^2.3.2",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.3.5"
},
"peerDependencies": {
"@solana/web3.js": "^1.24.0"
},
"dependencies": {
"@certusone/wormhole-sdk": "file:../../../sdk/js",
"@improbable-eng/grpc-web-node-http-transport": "^0.14.1"
},
"bugs": {
"url": "https://github.com/certusone/wormhole/issues"
},
"homepage": "https://github.com/certusone/wormhole#readme"
}

View File

@ -1,2 +0,0 @@
const copydir = require("copy-dir");
copydir.sync("../../../ethereum/build/contracts", "./contracts");

View File

@ -1,17 +0,0 @@
const find = require("find");
const fs = require("fs");
const path = require("path");
const SOURCE_ROOT = "src";
const TARGET_ROOT = "lib";
find.eachfile(/\.d\.ts(\..*)?/, SOURCE_ROOT, fname => {
fname_copy = fname.replace(SOURCE_ROOT, TARGET_ROOT);
console.log("copying types:", fname, "to", fname_copy);
fs.mkdirSync(path.dirname(fname_copy), {recursive: true});
fs.copyFileSync(fname, fname_copy);
});

View File

@ -1,17 +0,0 @@
const find = require("find");
const fs = require("fs");
const path = require("path");
const SOURCE_ROOT = "src";
const TARGET_ROOT = "lib";
find.eachfile(/\.wasm(\..*)?/, SOURCE_ROOT, fname => {
fname_copy = fname.replace(SOURCE_ROOT, TARGET_ROOT);
console.log("copyWasm:", fname, "to", fname_copy);
fs.mkdirSync(path.dirname(fname_copy), {recursive: true});
fs.copyFileSync(fname, fname_copy);
});

View File

@ -1,42 +0,0 @@
import { getSignedVAA, CHAIN_ID_SOLANA} from "@certusone/wormhole-sdk";
import { zeroPad } from "ethers/lib/utils";
import { PublicKey} from "@solana/web3.js";
var P2W_INSTANCE: any = undefined;
// Import p2w wasm bindings; be smart about it
export async function p2w_core(): Promise<any> {
// Only import once if P2W wasm is needed
if (!P2W_INSTANCE) {
P2W_INSTANCE = await import("./solana/p2w-core/pyth2wormhole");
}
return P2W_INSTANCE;
}
export function sol_addr2buf(addr: string): Buffer {
return Buffer.from(zeroPad(new PublicKey(addr).toBytes(), 32));
}
export async function getSignedAttestation(host: string, p2w_addr: string, sequence: number, extraGrpcOpts = {}): Promise<any>
{
const p2w = await p2w_core();
let emitter = p2w.get_emitter_address(p2w_addr);
let emitterHex = sol_addr2buf(emitter).toString("hex");
return await getSignedVAA(host, CHAIN_ID_SOLANA, emitterHex, "" + sequence, extraGrpcOpts);
}
export async function parseAttestation(vaa_payload: Uint8Array): Promise<any> {
const p2w = await p2w_core();
return await p2w.parse_attestation(vaa_payload);
}
export async function parseBatchAttestation(vaa_payload: Uint8Array): Promise<any> {
const p2w = await p2w_core();
console.log("p2w.parse_batch_attestaion is", p2w.parse_batch_attestation);
return await p2w.parse_batch_attestation(vaa_payload);
}

View File

@ -1,17 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"declaration": true,
"outDir": "./lib",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"downlevelIteration": true,
"allowJs": true,
},
"types": [],
"include": ["src", "types"],
"exclude": ["node_modules", "**/__tests__/*"]
}

View File

@ -1,9 +0,0 @@
{
"extends": ["tslint:recommended", "tslint-config-prettier"],
"linterOptions": {
"exclude": [
"src/proto/**"
]
}
}

View File

@ -1,239 +0,0 @@
#!/usr/bin/env python3
# This script sets up a simple loop for periodical attestation of Pyth data
import json
import logging
import os
import re
import sys
import threading
import time
from http.client import HTTPConnection
from http.server import BaseHTTPRequestHandler, HTTPServer
from pyth_utils import *
logging.basicConfig(
level=logging.DEBUG, format="%(asctime)s | %(module)s | %(levelname)s | %(message)s"
)
P2W_SOL_ADDRESS = os.environ.get(
"P2W_SOL_ADDRESS", "P2WH424242424242424242424242424242424242424"
)
P2W_ATTEST_INTERVAL = float(os.environ.get("P2W_ATTEST_INTERVAL", 5))
P2W_OWNER_KEYPAIR = os.environ.get(
"P2W_OWNER_KEYPAIR", "/usr/src/solana/keys/p2w_owner.json"
)
P2W_ATTESTATIONS_PORT = int(os.environ.get("P2W_ATTESTATIONS_PORT", 4343))
P2W_INITIALIZE_SOL_CONTRACT = os.environ.get("P2W_INITIALIZE_SOL_CONTRACT", None)
PYTH_TEST_ACCOUNTS_HOST = "pyth"
PYTH_TEST_ACCOUNTS_PORT = 4242
P2W_ATTESTATION_CFG = os.environ.get("P2W_ATTESTATION_CFG", None)
WORMHOLE_ADDRESS = os.environ.get(
"WORMHOLE_ADDRESS", "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
)
ATTESTATIONS = {
"pendingSeqnos": [],
}
class P2WAutoattestStatusEndpoint(BaseHTTPRequestHandler):
"""
A dumb endpoint for last attested price metadata.
"""
def do_GET(self):
logging.info(f"Got path {self.path}")
sys.stdout.flush()
data = json.dumps(ATTESTATIONS).encode("utf-8")
logging.debug(f"Sending: {data}")
ATTESTATIONS["pendingSeqnos"] = []
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
self.wfile.flush()
def serve_attestations():
"""
Run a barebones HTTP server to share Pyth2wormhole attestation history
"""
server_address = ("", P2W_ATTESTATIONS_PORT)
httpd = HTTPServer(server_address, P2WAutoattestStatusEndpoint)
httpd.serve_forever()
if SOL_AIRDROP_AMT > 0:
# Fund the p2w owner
sol_run_or_die("airdrop", [
str(SOL_AIRDROP_AMT),
"--keypair", P2W_OWNER_KEYPAIR,
"--commitment", "finalized",
])
if P2W_INITIALIZE_SOL_CONTRACT is not None:
# Get actor pubkeys
P2W_OWNER_ADDRESS = sol_run_or_die(
"address", ["--keypair", P2W_OWNER_KEYPAIR], capture_output=True
).stdout.strip()
PYTH_OWNER_ADDRESS = sol_run_or_die(
"address", ["--keypair", PYTH_PROGRAM_KEYPAIR], capture_output=True
).stdout.strip()
init_result = run_or_die(
[
"pyth2wormhole-client",
"--log-level",
"4",
"--p2w-addr",
P2W_SOL_ADDRESS,
"--rpc-url",
SOL_RPC_URL,
"--payer",
P2W_OWNER_KEYPAIR,
"init",
"--wh-prog",
WORMHOLE_ADDRESS,
"--owner",
P2W_OWNER_ADDRESS,
"--pyth-owner",
PYTH_OWNER_ADDRESS,
],
capture_output=True,
die=False,
)
if init_result.returncode != 0:
logging.error(
"NOTE: pyth2wormhole-client init failed, retrying with set_config"
)
run_or_die(
[
"pyth2wormhole-client",
"--log-level",
"4",
"--p2w-addr",
P2W_SOL_ADDRESS,
"--rpc-url",
SOL_RPC_URL,
"--payer",
P2W_OWNER_KEYPAIR,
"set-config",
"--owner",
P2W_OWNER_KEYPAIR,
"--new-owner",
P2W_OWNER_ADDRESS,
"--new-wh-prog",
WORMHOLE_ADDRESS,
"--new-pyth-owner",
PYTH_OWNER_ADDRESS,
],
capture_output=True,
)
# Retrieve available symbols from the test pyth publisher if not provided in envs
if P2W_ATTESTATION_CFG is None:
P2W_ATTESTATION_CFG = "./attestation_cfg_test.yaml"
conn = HTTPConnection(PYTH_TEST_ACCOUNTS_HOST, PYTH_TEST_ACCOUNTS_PORT)
conn.request("GET", "/")
res = conn.getresponse()
pyth_accounts = None
if res.getheader("Content-Type") == "application/json":
pyth_accounts = json.load(res)
else:
logging.error("Bad Content type")
sys.exit(1)
cfg_yaml = f"""
---
symbols:"""
logging.info(f"Retrieved {len(pyth_accounts)} Pyth accounts from endpoint: {pyth_accounts}")
for acc in pyth_accounts:
name = acc["name"]
price = acc["price"]
product = acc["product"]
cfg_yaml += f"""
- name: {name}
price_addr: {price}
product_addr: {product}"""
with open(P2W_ATTESTATION_CFG, "w") as f:
f.write(cfg_yaml)
f.flush()
attest_result = run_or_die(
[
"pyth2wormhole-client",
"--log-level",
"4",
"--p2w-addr",
P2W_SOL_ADDRESS,
"--rpc-url",
SOL_RPC_URL,
"--payer",
P2W_OWNER_KEYPAIR,
"attest",
"-f",
P2W_ATTESTATION_CFG
],
capture_output=True,
)
logging.info("p2w_autoattest ready to roll!")
logging.info(f"Attest Interval: {P2W_ATTEST_INTERVAL}")
# Serve p2w endpoint
endpoint_thread = threading.Thread(target=serve_attestations, daemon=True)
endpoint_thread.start()
# Let k8s know the service is up
readiness_thread = threading.Thread(target=readiness, daemon=True)
readiness_thread.start()
seqno_regex = re.compile(r"Sequence number: (\d+)")
while True:
matches = seqno_regex.findall(attest_result.stdout)
seqnos = list(map(lambda m: int(m), matches))
ATTESTATIONS["pendingSeqnos"] += seqnos
logging.info(f"{len(seqnos)} batch seqno(s) received: {seqnos})")
attest_result = run_or_die(
[
"pyth2wormhole-client",
"--log-level",
"4",
"--p2w-addr",
P2W_SOL_ADDRESS,
"--rpc-url",
SOL_RPC_URL,
"--payer",
P2W_OWNER_KEYPAIR,
"attest",
"-f",
P2W_ATTESTATION_CFG
],
capture_output=True,
)
time.sleep(P2W_ATTEST_INTERVAL)

View File

@ -1,135 +0,0 @@
#!/usr/bin/env python3
from pyth_utils import *
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import os
import random
import sys
import threading
import time
PYTH_TEST_SYMBOL_COUNT = int(os.environ.get("PYTH_TEST_SYMBOL_COUNT", "9"))
class PythAccEndpoint(BaseHTTPRequestHandler):
"""
A dumb endpoint to respond with a JSON containing Pyth account addresses
"""
def do_GET(self):
print(f"Got path {self.path}")
sys.stdout.flush()
data = json.dumps(TEST_SYMBOLS).encode("utf-8")
print(f"Sending:\n{data}")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
self.wfile.flush()
TEST_SYMBOLS = []
def publisher_random_update(price_pubkey):
"""
Update the specified price with random values
"""
value = random.randrange(1024)
confidence = 5
pyth_run_or_die("upd_price_val", args=[
price_pubkey, str(value), str(confidence), "trading"
])
print(f"Price {price_pubkey} value updated to {str(value)}!")
def accounts_endpoint():
"""
Run a barebones HTTP server to share the dynamic Pyth
mapping/product/price account addresses
"""
server_address = ('', 4242)
httpd = HTTPServer(server_address, PythAccEndpoint)
httpd.serve_forever()
# Fund the publisher
sol_run_or_die("airdrop", [
str(SOL_AIRDROP_AMT),
"--keypair", PYTH_PUBLISHER_KEYPAIR,
"--commitment", "finalized",
])
# Create a mapping
pyth_run_or_die("init_mapping")
print(f"Creating {PYTH_TEST_SYMBOL_COUNT} test Pyth symbols")
publisher_pubkey = sol_run_or_die("address", args=[
"--keypair", PYTH_PUBLISHER_KEYPAIR
], capture_output=True).stdout.strip()
for i in range(PYTH_TEST_SYMBOL_COUNT):
symbol_name = f"Test symbol {i}"
# Add a product
prod_pubkey = pyth_run_or_die(
"add_product", capture_output=True).stdout.strip()
print(f"{symbol_name}: Added product {prod_pubkey}")
# Add a price
price_pubkey = pyth_run_or_die(
"add_price",
args=[prod_pubkey, "price"],
confirm=False,
capture_output=True
).stdout.strip()
print(f"{symbol_name}: Added price {price_pubkey}")
# Become a publisher for the new price
pyth_run_or_die(
"add_publisher", args=[publisher_pubkey, price_pubkey],
confirm=False,
debug=True,
capture_output=True)
print(f"{symbol_name}: Added publisher {publisher_pubkey}")
# Update the prices as the newly added publisher
publisher_random_update(price_pubkey)
sym = {
"name": symbol_name,
"product": prod_pubkey,
"price": price_pubkey
}
TEST_SYMBOLS.append(sym)
sys.stdout.flush()
print(
f"Mock updates ready to roll. Updating every {str(PYTH_PUBLISHER_INTERVAL)} seconds")
# Spin off the readiness probe endpoint into a separate thread
readiness_thread = threading.Thread(target=readiness, daemon=True)
# Start an HTTP endpoint for looking up test product/price addresses
http_service = threading.Thread(target=accounts_endpoint, daemon=True)
readiness_thread.start()
http_service.start()
while True:
for sym in TEST_SYMBOLS:
publisher_random_update(sym["price"])
time.sleep(PYTH_PUBLISHER_INTERVAL)
sys.stdout.flush()
readiness_thread.join()
http_service.join()

View File

@ -1,96 +0,0 @@
import os
import socketserver
import subprocess
import sys
# Settings specific to local devnet Pyth instance
PYTH = os.environ.get("PYTH", "./pyth")
PYTH_KEY_STORE = os.environ.get("PYTH_KEY_STORE", "/home/pyth/.pythd")
PYTH_PROGRAM_KEYPAIR = os.environ.get(
"PYTH_PROGRAM_KEYPAIR", f"{PYTH_KEY_STORE}/publish_key_pair.json"
)
PYTH_PROGRAM_SO_PATH = os.environ.get("PYTH_PROGRAM_SO", "../target/oracle.so")
PYTH_PUBLISHER_KEYPAIR = os.environ.get(
"PYTH_PUBLISHER_KEYPAIR", f"{PYTH_KEY_STORE}/publish_key_pair.json"
)
PYTH_PUBLISHER_INTERVAL = float(os.environ.get("PYTH_PUBLISHER_INTERVAL", "5"))
# 0 setting disables airdropping
SOL_AIRDROP_AMT = int(os.environ.get("SOL_AIRDROP_AMT", 0))
# SOL RPC settings
SOL_RPC_HOST = os.environ.get("SOL_RPC_HOST", "solana-devnet")
SOL_RPC_PORT = int(os.environ.get("SOL_RPC_PORT", 8899))
SOL_RPC_URL = os.environ.get(
"SOL_RPC_URL", "http://{0}:{1}".format(SOL_RPC_HOST, SOL_RPC_PORT)
)
# A TCP port we open when a service is ready
READINESS_PORT = int(os.environ.get("READINESS_PORT", "2000"))
def run_or_die(args, die=True, **kwargs):
"""
Opinionated subprocess.run() call with fancy logging
"""
args_readable = " ".join(args)
print(f"CMD RUN\t{args_readable}", file=sys.stderr)
sys.stderr.flush()
ret = subprocess.run(args, text=True, **kwargs)
if ret.returncode != 0:
print(f"CMD FAIL {ret.returncode}\t{args_readable}", file=sys.stderr)
out = ret.stdout if ret.stdout is not None else "<not captured>"
err = ret.stderr if ret.stderr is not None else "<not captured>"
print(f"CMD STDOUT\n{out}", file=sys.stderr)
print(f"CMD STDERR\n{err}", file=sys.stderr)
if die:
sys.exit(ret.returncode)
else:
print(f'{"CMD DIE FALSE"}', file=sys.stderr)
else:
print(f"CMD OK\t{args_readable}", file=sys.stderr)
sys.stderr.flush()
return ret
def pyth_run_or_die(subcommand, args=[], debug=False, confirm=True, **kwargs):
"""
Pyth boilerplate in front of run_or_die
"""
return run_or_die(
[PYTH, subcommand] + args + (["-d"] if debug else [])
# Note: not all pyth subcommands accept -n
+ ([] if confirm else ["-n"])
+ ["-k", PYTH_KEY_STORE]
+ ["-r", SOL_RPC_HOST]
+ ["-c", "finalized"],
**kwargs,
)
def sol_run_or_die(subcommand, args=[], **kwargs):
"""
Solana boilerplate in front of run_or_die
"""
return run_or_die(["solana", subcommand] + args + ["--url", SOL_RPC_URL], **kwargs)
class ReadinessTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
"""TCP black hole"""
self.rfile.read(64)
def readiness():
"""
Accept connections from readiness probe
"""
with socketserver.TCPServer(
("0.0.0.0", READINESS_PORT), ReadinessTCPHandler
) as srv:
srv.serve_forever()