remove pyth2wormhole
This commit is contained in:
parent
f44837578b
commit
a3272dce4a
50
Tiltfile
50
Tiltfile
|
@ -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(
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
{}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
_;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
|
@ -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]
|
|
@ -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
|
@ -1,4 +0,0 @@
|
|||
[workspace]
|
||||
members = ["client", "program"]
|
||||
[patch.crates-io]
|
||||
memmap2 = { path = "../bridge/memmap2-rs" }
|
|
@ -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"}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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"
|
|
@ -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}
|
|
@ -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(())
|
||||
}
|
|
@ -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">;
|
|
@ -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(())
|
||||
}
|
|
@ -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;
|
|
@ -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(())
|
||||
}
|
|
@ -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(×tamp.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(())
|
||||
}
|
||||
}
|
|
@ -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 })
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
|
@ -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(),
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
#[cfg(test)]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod contract;
|
||||
pub mod msg;
|
||||
pub mod state;
|
||||
pub mod types;
|
|
@ -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 },
|
||||
}
|
|
@ -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 })
|
||||
}
|
||||
}
|
|
@ -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(×tamp.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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 })
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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"
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
const copydir = require("copy-dir");
|
||||
copydir.sync("../../../ethereum/build/contracts", "./contracts");
|
|
@ -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);
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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);});
|
||||
}
|
||||
|
||||
})();
|
|
@ -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__/*"]
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
{
|
||||
"extends": ["tslint:recommended", "tslint-config-prettier"],
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
"src/proto/**"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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"
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
const copydir = require("copy-dir");
|
||||
copydir.sync("../../../ethereum/build/contracts", "./contracts");
|
|
@ -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);
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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);
|
||||
}
|
|
@ -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__/*"]
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
{
|
||||
"extends": ["tslint:recommended", "tslint-config-prettier"],
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
"src/proto/**"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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()
|
|
@ -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()
|
Loading…
Reference in New Issue