near: initial commit
This commit is contained in:
parent
536a97066e
commit
4fe9841810
|
@ -45,3 +45,4 @@ COPY --from=const-build /scripts/.env.hex solana/.env
|
|||
COPY --from=const-build /scripts/.env.hex terra/tools/.env
|
||||
COPY --from=const-build /scripts/.env.hex cosmwasm/tools/.env
|
||||
COPY --from=const-build /scripts/.env.hex algorand/.env
|
||||
COPY --from=const-build /scripts/.env.hex near/.env
|
||||
|
|
39
Tiltfile
39
Tiltfile
|
@ -38,6 +38,7 @@ config.define_string("bigTableKeyPath", False, "Path to BigTable json key file")
|
|||
config.define_string("webHost", False, "Public hostname for port forwards")
|
||||
|
||||
# Components
|
||||
config.define_bool("near", False, "Enable Near component")
|
||||
config.define_bool("algorand", False, "Enable Algorand component")
|
||||
config.define_bool("evm2", False, "Enable second Eth component")
|
||||
config.define_bool("solana", False, "Enable Solana component")
|
||||
|
@ -60,6 +61,7 @@ gcpProject = cfg.get("gcpProject", "local-dev")
|
|||
bigTableKeyPath = cfg.get("bigTableKeyPath", "./event_database/devnet_key.json")
|
||||
webHost = cfg.get("webHost", "localhost")
|
||||
algorand = cfg.get("algorand", True)
|
||||
near = cfg.get("near", True)
|
||||
evm2 = cfg.get("evm2", True)
|
||||
solana = cfg.get("solana", True)
|
||||
terra_classic = cfg.get("terra_classic", True)
|
||||
|
@ -231,6 +233,14 @@ def build_node_yaml():
|
|||
"--chainGovernorEnabled"
|
||||
]
|
||||
|
||||
if near:
|
||||
container["command"] += [
|
||||
"--nearRPC",
|
||||
"http://near:3030",
|
||||
"--nearContract",
|
||||
"wormhole.test.near"
|
||||
]
|
||||
|
||||
return encode_yaml_stream(node_yaml)
|
||||
|
||||
k8s_yaml_with_ns(build_node_yaml())
|
||||
|
@ -240,6 +250,8 @@ if evm2:
|
|||
guardian_resource_deps = guardian_resource_deps + ["eth-devnet2"]
|
||||
if solana:
|
||||
guardian_resource_deps = guardian_resource_deps + ["solana-devnet"]
|
||||
if near:
|
||||
guardian_resource_deps = guardian_resource_deps + ["near"]
|
||||
if terra_classic:
|
||||
guardian_resource_deps = guardian_resource_deps + ["terra-terrad"]
|
||||
if terra2:
|
||||
|
@ -732,3 +744,30 @@ if algorand:
|
|||
trigger_mode = trigger_mode,
|
||||
)
|
||||
|
||||
|
||||
if near:
|
||||
k8s_yaml_with_ns("devnet/near-devnet.yaml")
|
||||
|
||||
docker_build(
|
||||
ref = "near-node",
|
||||
context = "near",
|
||||
dockerfile = "near/Dockerfile",
|
||||
only = ["Dockerfile", "node_builder.sh", "start_node.sh", "README.md", "cert.pem"],
|
||||
)
|
||||
|
||||
docker_build(
|
||||
ref = "near-contracts",
|
||||
context = "near",
|
||||
dockerfile = "near/Dockerfile.contracts",
|
||||
)
|
||||
|
||||
k8s_resource(
|
||||
"near",
|
||||
port_forwards = [
|
||||
port_forward(3030, name = "Node [:3030]", host = webHost),
|
||||
port_forward(3031, name = "webserver [:3031]", host = webHost),
|
||||
],
|
||||
resource_deps = ["const-gen"],
|
||||
labels = ["near"],
|
||||
trigger_mode = trigger_mode,
|
||||
)
|
||||
|
|
|
@ -656,7 +656,7 @@ function parseAddress(chain: ChainName, address: string): string {
|
|||
// TODO: is there a better native format for algorand?
|
||||
return "0x" + evm_address(address);
|
||||
} else if (chain === "near") {
|
||||
return "0x" + evm_address(address);
|
||||
return "0x" + hex(address).substring(2).padStart(64, "0")
|
||||
} else if (chain === "injective") {
|
||||
throw Error("INJECTIVE is not supported yet");
|
||||
} else if (chain === "osmosis") {
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: near
|
||||
name: near
|
||||
spec:
|
||||
ports:
|
||||
- name: node
|
||||
port: 3030
|
||||
targetPort: node
|
||||
selector:
|
||||
app: near
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
labels:
|
||||
app: near
|
||||
name: near
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: near
|
||||
serviceName: near
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: near
|
||||
spec:
|
||||
containers:
|
||||
- name: near-node
|
||||
image: near-node
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- /tmp/start_node.sh
|
||||
ports:
|
||||
- containerPort: 3030
|
||||
name: node
|
||||
protocol: TCP
|
||||
- containerPort: 3031
|
||||
name: webserver
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 3030
|
||||
- name: near-contracts
|
||||
image: near-contracts
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "sh devnet_deploy.sh && touch success && sleep infinity"
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 3030
|
||||
periodSeconds: 1
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 5
|
||||
|
||||
restartPolicy: Always
|
|
@ -115,6 +115,10 @@ spec:
|
|||
# - http://algorand:4001
|
||||
# - --algorandAlgodToken
|
||||
# - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
# - --nearRPC
|
||||
# - http://near:3030
|
||||
# - --nearContract
|
||||
# - wormhole.test.near
|
||||
- --solanaContract
|
||||
- Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o
|
||||
# - --solanaWS
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
_sandbox
|
|
@ -0,0 +1,23 @@
|
|||
FROM rust:1.60@sha256:48d3b5baf199dc7c378e775c47b0c40aaf7d8b23eaf67e15b095bbdaaecd1f10 as near-node
|
||||
|
||||
# Support additional root CAs
|
||||
COPY README.md cert.pem* /certs/
|
||||
# Debian
|
||||
RUN if [ -e /certs/cert.pem ]; then cp /certs/cert.pem /etc/ssl/certs/ca-certificates.crt; fi
|
||||
# git
|
||||
RUN if [ -e /certs/cert.pem ]; then git config --global http.sslCAInfo /certs/cert.pem; fi
|
||||
|
||||
RUN rustup update
|
||||
RUN apt-get install python3 --no-install-recommends
|
||||
|
||||
COPY node_builder.sh /tmp
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
RUN ./node_builder.sh
|
||||
|
||||
COPY start_node.sh /tmp
|
||||
|
||||
RUN rm -rf /tmp/_sandbox
|
||||
RUN mkdir -p /tmp/sandbox
|
||||
RUN nearcore/target/release/near-sandbox --home /tmp/_sandbox init
|
|
@ -0,0 +1,30 @@
|
|||
FROM debian@sha256:2ce44bbc00a79113c296d9d25524e15d423b23303fdbbe20190d2f96e0aeb251 as near-contracts
|
||||
|
||||
# Support additional root CAs
|
||||
COPY README.md cert.pem* /certs/
|
||||
# Debian
|
||||
RUN if [ -e /certs/cert.pem ]; then cp /certs/cert.pem /etc/ssl/certs/ca-certificates.crt; fi
|
||||
# git
|
||||
RUN if [ -e /certs/cert.pem ]; then git config --global http.sslCAInfo /certs/cert.pem; fi
|
||||
|
||||
RUN apt-get update && apt-get install apt-utils && apt-get install -y python3 npm curl --no-install-recommends
|
||||
|
||||
ADD setup-rust.sh .
|
||||
RUN ./setup-rust.sh
|
||||
|
||||
RUN mkdir -p /.npm /home/node/appa /home/node/.npm
|
||||
WORKDIR /home/node/app
|
||||
RUN chown -R 1000:1000 /home/node
|
||||
RUN chown -R 1000:1000 /.npm
|
||||
|
||||
USER 1000
|
||||
|
||||
ADD --chown=1000:1000 package.json .
|
||||
ADD --chown=1000:1000 package-lock.json .
|
||||
ADD --chown=1000:1000 .env .env
|
||||
|
||||
RUN npm ci
|
||||
|
||||
ADD --chown=1000:1000 devnet_deploy.* .
|
||||
ADD --chown=1000:1000 ./contracts/*/target/wasm32-unknown-unknown/release/*.wasm .
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
.PHONY: test
|
||||
test: build node_modules
|
||||
npx ts-node test/test.ts
|
||||
|
||||
all: node_modules build nearcore
|
||||
|
||||
build: contracts/ft/target/wasm32-unknown-unknown/release/near_ft.wasm \
|
||||
contracts/mock-bridge-integration/target/wasm32-unknown-unknown/release/near_mock_bridge_integration.wasm \
|
||||
contracts/mock-bridge-token/target/wasm32-unknown-unknown/release/near_mock_bridge_token.wasm \
|
||||
contracts/nft-bridge/target/wasm32-unknown-unknown/release/near_nft_bridge.wasm \
|
||||
contracts/nft-wrapped/target/wasm32-unknown-unknown/release/near_nft.wasm \
|
||||
contracts/token-bridge/target/wasm32-unknown-unknown/release/near_token_bridge.wasm \
|
||||
contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm
|
||||
|
||||
contracts/ft/target/wasm32-unknown-unknown/release/near_ft.wasm: contracts/ft/src/*.rs contracts/ft/Cargo.toml
|
||||
cd contracts/ft; cargo build --target wasm32-unknown-unknown --release
|
||||
|
||||
contracts/mock-bridge-integration/target/wasm32-unknown-unknown/release/near_mock_bridge_integration.wasm: \
|
||||
contracts/mock-bridge-integration/src/*.rs \
|
||||
contracts/mock-bridge-integration/Cargo.toml \
|
||||
contracts/mock-bridge-token/target/wasm32-unknown-unknown/release/near_mock_bridge_token.wasm \
|
||||
contracts/nft-wrapped/target/wasm32-unknown-unknown/release/near_nft.wasm
|
||||
|
||||
cd contracts/mock-bridge-integration; cargo build --target wasm32-unknown-unknown --release
|
||||
|
||||
contracts/mock-bridge-token/target/wasm32-unknown-unknown/release/near_mock_bridge_token.wasm : contracts/mock-bridge-token/src/*.rs contracts/mock-bridge-token/Cargo.toml
|
||||
cd contracts/mock-bridge-token; cargo build --target wasm32-unknown-unknown --release
|
||||
|
||||
contracts/nft-bridge/target/wasm32-unknown-unknown/release/near_nft_bridge.wasm: contracts/nft-bridge/src/*.rs contracts/nft-bridge/Cargo.toml contracts/nft-wrapped/target/wasm32-unknown-unknown/release/near_nft.wasm
|
||||
cd contracts/nft-bridge; cargo build --target wasm32-unknown-unknown --release
|
||||
|
||||
contracts/nft-wrapped/target/wasm32-unknown-unknown/release/near_nft.wasm: contracts/nft-wrapped/src/*.rs contracts/nft-wrapped/Cargo.toml
|
||||
cd contracts/nft-wrapped; cargo build --target wasm32-unknown-unknown --release
|
||||
|
||||
contracts/token-bridge/target/wasm32-unknown-unknown/release/near_token_bridge.wasm: contracts/token-bridge/src/*.rs contracts/token-bridge/Cargo.toml contracts/ft/target/wasm32-unknown-unknown/release/near_ft.wasm
|
||||
cd contracts/token-bridge; cargo build --target wasm32-unknown-unknown --release
|
||||
|
||||
contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm: contracts/wormhole/src/*.rs contracts/wormhole/Cargo.toml
|
||||
cd contracts/wormhole; cargo build --target wasm32-unknown-unknown --release
|
||||
|
||||
package-lock.json: package.json
|
||||
npm install
|
||||
|
||||
node_modules: package-lock.json
|
||||
touch -m node_modules
|
||||
npm ci
|
||||
|
||||
nearcore:
|
||||
mkdir $@ && \
|
||||
cd $@ && \
|
||||
git init && \
|
||||
git remote add origin https://github.com/near/nearcore && \
|
||||
git fetch --depth 1 origin c6eb78ab11d0fb7c7bb9dfa6d712aba449a0140b && \
|
||||
git checkout FETCH_HEAD
|
||||
cd $@ && make sandbox-release
|
||||
|
||||
run: nearcore
|
||||
-killall -q Python
|
||||
./start_node.sh
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf nearcore node_modules contracts/*/target
|
||||
|
||||
|
||||
.PHONY: reset-force
|
||||
reset-force: build
|
||||
-echo cleaning and restarting state
|
||||
-kubectl delete pod guardian-0 --force &
|
||||
-kubectl delete pod algorand-0 --force &
|
||||
-kubectl delete pod near-0 --force &
|
||||
-echo done
|
||||
|
||||
.PHONY: reset
|
||||
reset: build
|
||||
-echo cleaning and restarting state
|
||||
-kubectl delete pod guardian-0 &
|
||||
-kubectl delete pod algorand-0 &
|
||||
-kubectl delete pod near-0 &
|
||||
-echo done
|
||||
|
||||
.PHONY: cycle
|
||||
cycle: reset
|
||||
(cd ../sdk/js; npm run build)
|
||||
npm ci
|
||||
ts-node test/sdk.ts
|
||||
|
||||
.PHONY: testnet
|
||||
testnet: build
|
||||
cp contracts/*/target/wasm32-unknown-unknown/release/*.wasm .
|
||||
ts-node devnet_deploy.ts
|
|
@ -0,0 +1,155 @@
|
|||
01000000000100bc708675768ba853af2e01c6217317fbb68500a508a39fece15dac27d36c7c804cf94ad9e4b2134209bbdad7c9479d9929c60bda4612a74c61c06efebacc4da401000003cb5d690100000200000000000000000000000026b4afb60d6c903165150c6f0aa14f8016be4aec00000000000000010f01000000000000000000000000d1a269d9b0dfb66cfdaf89cf0c6e6f8df0615ad00002415045f09f9092000000000000000000000000000000000000000000000000004e6f7420616e2041504520f09f90920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a5268747470733a2f2f636c6f7564666c6172652d697066732e636f6d2f697066732f516d65536a53696e4870506e6d586d73704d6a776958794e367a533445397a63636172694752336a7863615774712f31301212121212121212121212121212121212121212121212121212121212121212000f
|
||||
|
||||
--
|
||||
|
||||
1) vaa is received for a user for a token where the user has not paid the storage deposit in that token
|
||||
2) vaa gets submitted without using the SDK which detects this and correctly registers/pays-storage-deposit
|
||||
|
||||
choice 1:
|
||||
|
||||
VAA gets consumed but the tokens don't get transfered
|
||||
|
||||
choice 2:
|
||||
|
||||
The token bridge proactively throws money at the token, which it hopes will get refunded,
|
||||
to pay for storage if it is not already there
|
||||
|
||||
VAA gets consumed but the money might not get refunded... due to token implementor being an asshat
|
||||
|
||||
|
||||
|
||||
|
||||
// demonstrates how to query the state without setting
|
||||
// up an account. (View methods only)
|
||||
const { providers } = require("near-api-js");
|
||||
//network config (replace testnet with mainnet or betanet)
|
||||
const provider = new providers.JsonRpcProvider("https://rpc.testnet.near.org");
|
||||
|
||||
getState();
|
||||
|
||||
async function getState() {
|
||||
const rawResult = await provider.query({
|
||||
request_type: "call_function",
|
||||
account_id: "guest-book.testnet",
|
||||
method_name: "getMessages",
|
||||
args_base64: "e30=",
|
||||
finality: "optimistic",
|
||||
});
|
||||
|
||||
// format result
|
||||
const res = JSON.parse(Buffer.from(rawResult.result).toString());
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
|
||||
import { Account as nearAccount } from "near-api-js";
|
||||
|
||||
My impression is:
|
||||
|
||||
https://docs.near.org/docs/tutorials/near-indexer
|
||||
|
||||
https://thewiki.near.page/events-api
|
||||
|
||||
==
|
||||
kubectl exec -it near-0 -c near-node -- /bin/bash
|
||||
|
||||
My NEAR notes so far...
|
||||
|
||||
If needed, install `Rust`:
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
|
||||
You need at least version 1.56 or later
|
||||
|
||||
rustup default 1.56
|
||||
rustup update
|
||||
rustup target add wasm32-unknown-unknown
|
||||
|
||||
If needed, install `near-cli`:
|
||||
|
||||
npm install near-cli -g
|
||||
|
||||
To install the npm dependencies of this test program
|
||||
|
||||
npm install
|
||||
|
||||
for the near sdk, we are dependent on 4.0.0 or later (where the ecrecover API is)
|
||||
|
||||
https://docs.rs/near-sdk/4.0.0/near_sdk/index.html
|
||||
near-sdk = { version = "4.0.0", features = ["unstable"] }
|
||||
|
||||
This has been stuck into Cargo.toml
|
||||
|
||||
to bring up the sandbox, start a tmux window and run
|
||||
|
||||
rm -rf _sandbox
|
||||
mkdir -p _sandbox
|
||||
near-sandbox --home _sandbox init
|
||||
near-sandbox --home _sandbox run
|
||||
|
||||
https://docs.near.org/docs/develop/contracts/sandbox
|
||||
|
||||
First thing, lets put this in a docker in Tilt..
|
||||
|
||||
vaa_verify?
|
||||
|
||||
near-sdk-rs/near-sdk/src/environment/env.rs: (still unstable)
|
||||
|
||||
/// Recovers an ECDSA signer address from a 32-byte message `hash` and a corresponding `signature`
|
||||
/// along with `v` recovery byte.
|
||||
///
|
||||
/// Takes in an additional flag to check for malleability of the signature
|
||||
/// which is generally only ideal for transactions.
|
||||
///
|
||||
/// Returns 64 bytes representing the public key if the recovery was successful.
|
||||
#[cfg(feature = "unstable")]
|
||||
pub fn ecrecover(
|
||||
hash: &[u8],
|
||||
signature: &[u8],
|
||||
v: u8,
|
||||
malleability_flag: bool,
|
||||
) -> Option<[u8; 64]> {
|
||||
unsafe {
|
||||
let return_code = sys::ecrecover(
|
||||
hash.len() as _,
|
||||
hash.as_ptr() as _,
|
||||
signature.len() as _,
|
||||
signature.as_ptr() as _,
|
||||
v as u64,
|
||||
malleability_flag as u64,
|
||||
ATOMIC_OP_REGISTER,
|
||||
);
|
||||
if return_code == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(read_register_fixed_64(ATOMIC_OP_REGISTER))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
you can look for test_ecrecover() in the same file...
|
||||
|
||||
When building the sandbox, it is on port 3030 and we will need access to the validator_key.json...
|
||||
|
||||
curl http://localhost:3031/validator_key.json
|
||||
|
||||
function getConfig(env) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
contractAccount: "wormhole.test.near",
|
||||
keyPath: "./_sandbox/validator_key.json",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
.function_call(
|
||||
b"new".to_vec(),
|
||||
ft,
|
||||
data.to_vec(),
|
||||
vaa.sequence,
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
-- bridge for nep141/nep148 tokens --
|
||||
|
||||
https://nomicon.io/Standards/Tokens/FungibleToken/Core#reference-level-explanation
|
||||
|
||||
|
||||
welcome to wormhole on near
|
||||
|
||||
all near tokens and $NEAR itself will go to the token account...
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "near-ft"
|
||||
version = "0.1.0"
|
||||
authors = ["Josh Siegel <jsiegel@jumptrading.com>"]
|
||||
edition = "2022"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
near-sdk = { version = "4.0.0" }
|
||||
near-contract-standards = { version = "4.0.0" }
|
||||
hex = { version = "0.4.3" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
debug = false
|
||||
panic = "abort"
|
||||
# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
|
||||
overflow-checks = true
|
||||
|
||||
[workspace]
|
||||
members = []
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
../target/wasm32-unknown-unknown/release/near_ft.wasm: *.rs ../Cargo.toml
|
||||
(cd ..; cargo build --target wasm32-unknown-unknown --release)
|
||||
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
use {
|
||||
near_contract_standards::fungible_token::{
|
||||
metadata::{
|
||||
FungibleTokenMetadata,
|
||||
FungibleTokenMetadataProvider,
|
||||
},
|
||||
FungibleToken,
|
||||
},
|
||||
near_sdk::{
|
||||
borsh::{
|
||||
self,
|
||||
BorshDeserialize,
|
||||
BorshSerialize,
|
||||
},
|
||||
collections::LazyOption,
|
||||
env,
|
||||
json_types::U128,
|
||||
near_bindgen,
|
||||
AccountId,
|
||||
Balance,
|
||||
PanicOnDefault,
|
||||
Promise,
|
||||
PromiseOrValue,
|
||||
StorageUsage,
|
||||
},
|
||||
};
|
||||
|
||||
const CHAIN_ID_NEAR: u16 = 15;
|
||||
|
||||
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
|
||||
pub struct FTContractMeta {
|
||||
metadata: FungibleTokenMetadata,
|
||||
vaa: Vec<u8>,
|
||||
sequence: u64,
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
|
||||
pub struct FTContract {
|
||||
token: FungibleToken,
|
||||
meta: LazyOption<FTContractMeta>,
|
||||
controller: AccountId,
|
||||
hash: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn get_string_from_32(v: &[u8]) -> String {
|
||||
let s = String::from_utf8_lossy(v);
|
||||
s.chars().filter(|c| c != &'\0').collect()
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
impl FTContract {
|
||||
fn on_account_closed(&mut self, account_id: AccountId, balance: Balance) {
|
||||
env::log_str(&format!("Closed @{} with {}", account_id, balance));
|
||||
}
|
||||
|
||||
fn on_tokens_burned(&mut self, account_id: AccountId, amount: Balance) {
|
||||
env::log_str(&format!("Account @{} burned {}", account_id, amount));
|
||||
}
|
||||
|
||||
#[init]
|
||||
pub fn new(metadata: FungibleTokenMetadata, asset_meta: Vec<u8>, seq_number: u64) -> Self {
|
||||
assert!(!env::state_exists(), "Already initialized");
|
||||
|
||||
metadata.assert_valid();
|
||||
|
||||
let meta = FTContractMeta {
|
||||
metadata,
|
||||
vaa: asset_meta,
|
||||
sequence: seq_number,
|
||||
};
|
||||
|
||||
let acct = env::current_account_id();
|
||||
let astr = acct.to_string();
|
||||
|
||||
Self {
|
||||
token: FungibleToken::new(b"ft".to_vec()),
|
||||
meta: LazyOption::new(b"md".to_vec(), Some(&meta)),
|
||||
controller: env::predecessor_account_id(),
|
||||
hash: env::sha256(astr.as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_ft(
|
||||
&mut self,
|
||||
metadata: FungibleTokenMetadata,
|
||||
asset_meta: Vec<u8>,
|
||||
seq_number: u64,
|
||||
) {
|
||||
if env::predecessor_account_id() != self.controller {
|
||||
env::panic_str("CrossContractInvalidCaller");
|
||||
}
|
||||
|
||||
if seq_number <= self.meta.get().unwrap().sequence {
|
||||
env::panic_str("AssetMetaDataRollback");
|
||||
}
|
||||
|
||||
let meta = FTContractMeta {
|
||||
metadata,
|
||||
vaa: asset_meta,
|
||||
sequence: seq_number,
|
||||
};
|
||||
|
||||
self.meta.replace(&meta);
|
||||
}
|
||||
|
||||
pub fn vaa_withdraw(
|
||||
&mut self,
|
||||
from: AccountId,
|
||||
amount: u128,
|
||||
receiver: String,
|
||||
chain: u16,
|
||||
fee: u128,
|
||||
payload: String,
|
||||
) -> String {
|
||||
if env::predecessor_account_id() != self.controller {
|
||||
env::panic_str("CrossContractInvalidCaller");
|
||||
}
|
||||
|
||||
let vaa = self.meta.get().unwrap().vaa;
|
||||
|
||||
let mut p = [
|
||||
// PayloadID uint8 = 1
|
||||
(if payload.is_empty() { 1 } else { 3 } as u8)
|
||||
.to_be_bytes()
|
||||
.to_vec(),
|
||||
// Amount uint256
|
||||
vec![0; 24],
|
||||
(amount as u64).to_be_bytes().to_vec(),
|
||||
//TokenAddress bytes32
|
||||
vaa[0..32].to_vec(),
|
||||
// TokenChain uint16
|
||||
vaa[32..34].to_vec(),
|
||||
// To bytes32
|
||||
vec![0; (64 - receiver.len()) / 2],
|
||||
hex::decode(receiver).unwrap(),
|
||||
// ToChain uint16
|
||||
(chain as u16).to_be_bytes().to_vec(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
if payload.is_empty() {
|
||||
p = [p, vec![0; 24], (fee as u64).to_be_bytes().to_vec()].concat();
|
||||
if p.len() != 133 {
|
||||
env::panic_str(&format!("paylod1 formatting errro len = {}", p.len()));
|
||||
}
|
||||
} else {
|
||||
p = [p, hex::decode(&payload).unwrap()].concat();
|
||||
if p.len() != (133 + (payload.len() / 2)) {
|
||||
env::panic_str(&format!("paylod3 formatting errro len = {}", p.len()));
|
||||
}
|
||||
}
|
||||
|
||||
self.token.internal_withdraw(&from, amount);
|
||||
|
||||
near_contract_standards::fungible_token::events::FtBurn {
|
||||
owner_id: &from,
|
||||
amount: &U128::from(amount),
|
||||
memo: Some("Wormhole burn"),
|
||||
}
|
||||
.emit();
|
||||
|
||||
hex::encode(p)
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn vaa_transfer(
|
||||
&mut self,
|
||||
amount: u128,
|
||||
account_id: AccountId,
|
||||
recipient_chain: u16,
|
||||
fee: u128,
|
||||
refund_to: AccountId,
|
||||
) -> Promise {
|
||||
if env::predecessor_account_id() != self.controller {
|
||||
env::panic_str("CrossContractInvalidCaller");
|
||||
}
|
||||
|
||||
if recipient_chain != CHAIN_ID_NEAR {
|
||||
env::panic_str("InvalidRecipientChain");
|
||||
}
|
||||
|
||||
if amount == 0 {
|
||||
env::panic_str("ZeroAmountWastesGas");
|
||||
}
|
||||
|
||||
if amount <= fee {
|
||||
env::panic_str("amount <= fee");
|
||||
}
|
||||
|
||||
let mut deposit: Balance = env::attached_deposit();
|
||||
|
||||
if !self.token.accounts.contains_key(&account_id) {
|
||||
let min_balance = self.storage_balance_bounds().min.0;
|
||||
if deposit < min_balance {
|
||||
env::panic_str("The attached deposit is less than the minimum storage balance");
|
||||
}
|
||||
|
||||
self.token.internal_register_account(&account_id);
|
||||
|
||||
deposit -= min_balance;
|
||||
}
|
||||
|
||||
self.token.internal_deposit(&account_id, amount - fee);
|
||||
|
||||
near_contract_standards::fungible_token::events::FtMint {
|
||||
owner_id: &account_id,
|
||||
amount: &U128::from(amount - fee),
|
||||
memo: Some("wormhole minted tokens"),
|
||||
}
|
||||
.emit();
|
||||
|
||||
if fee != 0 {
|
||||
self.token.internal_deposit(&env::signer_account_id(), fee);
|
||||
|
||||
near_contract_standards::fungible_token::events::FtMint {
|
||||
owner_id: &env::signer_account_id(),
|
||||
amount: &U128::from(fee),
|
||||
memo: Some("wormhole minted tokens"),
|
||||
}
|
||||
.emit();
|
||||
}
|
||||
|
||||
env::log_str("vaa_transfer called in ft");
|
||||
|
||||
Promise::new(refund_to).transfer(deposit)
|
||||
}
|
||||
|
||||
pub fn account_storage_usage(&self) -> StorageUsage {
|
||||
self.token.account_storage_usage
|
||||
}
|
||||
|
||||
/// Return true if the caller is either controller or self
|
||||
pub fn controller_or_self(&self) -> bool {
|
||||
let caller = env::predecessor_account_id();
|
||||
caller == self.controller || caller == env::current_account_id()
|
||||
}
|
||||
}
|
||||
|
||||
near_contract_standards::impl_fungible_token_core!(FTContract, token, on_tokens_burned);
|
||||
near_contract_standards::impl_fungible_token_storage!(FTContract, token, on_account_closed);
|
||||
|
||||
#[near_bindgen]
|
||||
impl FungibleTokenMetadataProvider for FTContract {
|
||||
fn ft_metadata(&self) -> FungibleTokenMetadata {
|
||||
self.meta.get().unwrap().metadata
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "near-mock-bridge-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Josh Siegel <jsiegel@jumptrading.com>"]
|
||||
edition = "2022"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
serde_json = "*"
|
||||
near-sdk = { version = "4.0.0", features = ["unstable"] }
|
||||
near-contract-standards = { version = "4.0.0" }
|
||||
hex = { version = "0.4.3" }
|
||||
near-sys = { version = "0.2.0" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
debug = false
|
||||
panic = "abort"
|
||||
# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
|
||||
overflow-checks = true
|
||||
|
||||
[workspace]
|
||||
members = []
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
../target/wasm32-unknown-unknown/release/near_mock_bridge_integration.wasm: *.rs ../Cargo.toml
|
||||
(cd ..; cargo build --target wasm32-unknown-unknown --release)
|
||||
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
#![allow(unused_variables)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use {
|
||||
near_contract_standards::non_fungible_token::{
|
||||
metadata::{
|
||||
NFTContractMetadata,
|
||||
TokenMetadata,
|
||||
NFT_METADATA_SPEC,
|
||||
},
|
||||
Token,
|
||||
TokenId,
|
||||
},
|
||||
near_sdk::{
|
||||
borsh::{
|
||||
self,
|
||||
BorshDeserialize,
|
||||
BorshSerialize,
|
||||
},
|
||||
env,
|
||||
ext_contract,
|
||||
json_types::{
|
||||
Base64VecU8,
|
||||
U128,
|
||||
},
|
||||
near_bindgen,
|
||||
utils::is_promise_success,
|
||||
AccountId,
|
||||
Balance,
|
||||
Promise,
|
||||
PromiseOrValue,
|
||||
},
|
||||
};
|
||||
|
||||
const BRIDGE_TOKEN_BINARY: &[u8] = include_bytes!(
|
||||
"../../mock-bridge-token/target/wasm32-unknown-unknown/release/near_mock_bridge_token.wasm"
|
||||
);
|
||||
|
||||
const BRIDGE_NFT_BINARY: &[u8] =
|
||||
include_bytes!("../../nft-wrapped/target/wasm32-unknown-unknown/release/near_nft.wasm");
|
||||
|
||||
/// Initial balance for the BridgeToken contract to cover storage and related.
|
||||
const BRIDGE_TOKEN_INIT_BALANCE: Balance = 5_860_000_000_000_000_000_000;
|
||||
|
||||
#[ext_contract(ext_worm_hole)]
|
||||
pub trait Wormhole {
|
||||
fn verify_vaa(&self, vaa: String) -> u32;
|
||||
fn publish_message(&self, data: String, nonce: u32) -> u64;
|
||||
}
|
||||
|
||||
#[ext_contract(ext_ft_contract)]
|
||||
pub trait MockFtContract {
|
||||
fn new() -> Self;
|
||||
fn airdrop(&self, a: AccountId, amount: u128);
|
||||
}
|
||||
|
||||
#[ext_contract(ext_wormhole)]
|
||||
pub trait MockWormhole {
|
||||
fn pass(&self) -> bool;
|
||||
}
|
||||
|
||||
#[ext_contract(ext_nft_contract)]
|
||||
pub trait MockNftContract {
|
||||
fn new(owner_id: AccountId, metadata: NFTContractMetadata, seq_number: u64) -> Self;
|
||||
fn nft_mint(
|
||||
&mut self,
|
||||
token_id: TokenId,
|
||||
token_owner_id: AccountId,
|
||||
token_metadata: TokenMetadata,
|
||||
refund_to: AccountId,
|
||||
) -> Token;
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
#[derive(BorshDeserialize, BorshSerialize)]
|
||||
pub struct TokenBridgeTest {
|
||||
cnt: u32,
|
||||
ft_name: String,
|
||||
}
|
||||
|
||||
impl Default for TokenBridgeTest {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cnt: 0,
|
||||
ft_name: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
impl TokenBridgeTest {
|
||||
#[payable]
|
||||
pub fn deploy_ft(&mut self, account: String) -> Promise {
|
||||
let a = AccountId::try_from(account).unwrap();
|
||||
|
||||
if self.ft_name == "" {
|
||||
let name = format!("b{}", env::block_height());
|
||||
|
||||
|
||||
let bridge_token_account = format!("{}.{}", name, env::current_account_id());
|
||||
|
||||
self.ft_name = bridge_token_account.clone();
|
||||
|
||||
let bridge_token_account_id: AccountId =
|
||||
AccountId::new_unchecked(bridge_token_account.clone());
|
||||
|
||||
let v = BRIDGE_TOKEN_BINARY.to_vec();
|
||||
|
||||
Promise::new(bridge_token_account_id.clone())
|
||||
.create_account()
|
||||
.transfer(BRIDGE_TOKEN_INIT_BALANCE + (v.len() as u128 * env::storage_byte_cost()))
|
||||
.add_full_access_key(env::signer_account_pk())
|
||||
.deploy_contract(v)
|
||||
// Lets initialize it with useful stuff
|
||||
.then(ext_ft_contract::ext(bridge_token_account_id.clone()).new())
|
||||
.then(
|
||||
ext_ft_contract::ext(bridge_token_account_id)
|
||||
.with_attached_deposit(BRIDGE_TOKEN_INIT_BALANCE)
|
||||
.airdrop(a, BRIDGE_TOKEN_INIT_BALANCE),
|
||||
)
|
||||
// And then lets tell us we are done!
|
||||
.then(Self::ext(env::current_account_id()).finish_deploy(bridge_token_account))
|
||||
} else {
|
||||
let bridge_token_account_id: AccountId = AccountId::new_unchecked(self.ft_name.clone());
|
||||
|
||||
ext_ft_contract::ext(bridge_token_account_id)
|
||||
.with_attached_deposit(BRIDGE_TOKEN_INIT_BALANCE)
|
||||
.airdrop(a, BRIDGE_TOKEN_INIT_BALANCE)
|
||||
.then(Self::ext(env::current_account_id()).finish_deploy(self.ft_name.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn chunker(&mut self, s: String) -> Promise {
|
||||
self.cnt += 1;
|
||||
|
||||
env::log_str(&format!(
|
||||
"mock-bridge-integration/{}#{}: amount: {} cnt: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
env::attached_deposit(),
|
||||
self.cnt
|
||||
));
|
||||
|
||||
Self::ext(env::current_account_id())
|
||||
.with_attached_deposit(env::attached_deposit())
|
||||
.chunks(s)
|
||||
.then(
|
||||
Self::ext(env::current_account_id())
|
||||
.refunder(env::predecessor_account_id(), env::attached_deposit()),
|
||||
)
|
||||
}
|
||||
|
||||
#[private]
|
||||
pub fn refunder(&mut self, refund_to: AccountId, amt: Balance) {
|
||||
if !is_promise_success() {
|
||||
env::log_str(&format!(
|
||||
"mock-bridge-integration/{}#{}: refunding {} to {}",
|
||||
file!(),
|
||||
line!(),
|
||||
amt,
|
||||
refund_to
|
||||
));
|
||||
Promise::new(refund_to).transfer(amt);
|
||||
}
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn chunks(&mut self, s: String) -> Promise {
|
||||
self.cnt += 1;
|
||||
|
||||
env::log_str(&format!(
|
||||
"mock-bridge-integration/{}#{}: amount: {} cnt: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
env::attached_deposit(),
|
||||
self.cnt
|
||||
));
|
||||
|
||||
ext_wormhole::ext(AccountId::new_unchecked("wormhole.test.near".to_string()))
|
||||
.with_attached_deposit(env::attached_deposit())
|
||||
.pass()
|
||||
.then(Self::ext(env::current_account_id()).thrower(s))
|
||||
}
|
||||
|
||||
pub fn thrower(&mut self, s: String) -> Promise {
|
||||
self.cnt += 1;
|
||||
|
||||
env::log_str(&format!(
|
||||
"mock-bridge-integration/{}#{}: amount: {} cnt: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
env::attached_deposit(),
|
||||
self.cnt
|
||||
));
|
||||
env::panic_str(&s);
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn deploy_nft(&mut self, account: String) -> Promise {
|
||||
let a = AccountId::try_from(account).unwrap();
|
||||
|
||||
let bridge_nft_account = format!("b{}.{}", env::block_height(), env::current_account_id());
|
||||
let bridge_nft_account_id: AccountId = AccountId::new_unchecked(bridge_nft_account.clone());
|
||||
|
||||
let v = BRIDGE_NFT_BINARY.to_vec();
|
||||
|
||||
let md = NFTContractMetadata {
|
||||
spec: NFT_METADATA_SPEC.to_string(),
|
||||
name: "RandomNFT".to_string(),
|
||||
symbol: "RNFT".to_string(),
|
||||
icon: None,
|
||||
base_uri: None,
|
||||
reference: None,
|
||||
reference_hash: None,
|
||||
};
|
||||
|
||||
Promise::new(bridge_nft_account_id.clone())
|
||||
.create_account()
|
||||
.transfer(env::attached_deposit())
|
||||
.add_full_access_key(env::signer_account_pk())
|
||||
.deploy_contract(v)
|
||||
// Lets initialize it with useful stuff
|
||||
.then(
|
||||
ext_nft_contract::ext(bridge_nft_account_id.clone())
|
||||
.with_unused_gas_weight(3)
|
||||
.new(env::current_account_id(), md, 0),
|
||||
)
|
||||
.then(Self::ext(env::current_account_id()).finish_deploy(bridge_nft_account))
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn mint_nft(
|
||||
&mut self,
|
||||
nft: AccountId,
|
||||
token_id: String,
|
||||
media: String,
|
||||
give_to: AccountId,
|
||||
) -> Promise {
|
||||
let md = TokenMetadata {
|
||||
title: Some("Phil ".to_string() + &token_id),
|
||||
description: Some("George ".to_string() + &token_id),
|
||||
media: Some(media.clone()),
|
||||
media_hash: Some(Base64VecU8::from(env::sha256(media.as_bytes()))),
|
||||
copies: Some(1u64),
|
||||
issued_at: None,
|
||||
expires_at: None,
|
||||
starts_at: None,
|
||||
updated_at: None,
|
||||
extra: None,
|
||||
reference: None,
|
||||
reference_hash: None,
|
||||
};
|
||||
|
||||
ext_nft_contract::ext(nft)
|
||||
.with_attached_deposit(BRIDGE_TOKEN_INIT_BALANCE)
|
||||
.nft_mint(token_id, give_to, md, env::current_account_id())
|
||||
}
|
||||
|
||||
pub fn ft_on_transfer(
|
||||
&mut self,
|
||||
sender_id: AccountId,
|
||||
amount: U128,
|
||||
msg: String,
|
||||
) -> PromiseOrValue<U128> {
|
||||
env::log_str(&msg);
|
||||
env::panic_str("ft_on_transfer");
|
||||
}
|
||||
|
||||
#[private]
|
||||
pub fn finish_deploy(&mut self, ret: String) -> String {
|
||||
if is_promise_success() {
|
||||
ret
|
||||
} else {
|
||||
env::panic_str("bad deploy");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn payload3(
|
||||
amount: u128,
|
||||
token_address: Vec<u8>,
|
||||
token_chain: u16,
|
||||
fee: u128,
|
||||
vaa: String,
|
||||
) {
|
||||
env::log_str(&format!(
|
||||
"mock-bridge-integration/{}#{}: amount: {} vaa: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
amount,
|
||||
vaa
|
||||
));
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn publish_message(&mut self, core: AccountId, p: String) -> Promise {
|
||||
ext_worm_hole::ext(core)
|
||||
.with_attached_deposit(env::attached_deposit())
|
||||
.publish_message(p, env::block_height() as u32)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "near-mock-bridge-token"
|
||||
version = "0.1.0"
|
||||
authors = ["Josh Siegel <jsiegel@jumptrading.com>"]
|
||||
edition = "2022"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
serde_json = "*"
|
||||
near-sdk = { version = "4.0.0", features = ["unstable"] }
|
||||
near-contract-standards = { version = "4.0.0" }
|
||||
hex = { version = "0.4.3" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
debug = false
|
||||
panic = "abort"
|
||||
# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
|
||||
overflow-checks = true
|
||||
|
||||
[workspace]
|
||||
members = []
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
../target/wasm32-unknown-unknown/release/near_mach_bridge_token.wasm: *.rs ../Cargo.toml
|
||||
(cd ..; cargo build --target wasm32-unknown-unknown --release)
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
use near_contract_standards::fungible_token::metadata::{
|
||||
FungibleTokenMetadata, FungibleTokenMetadataProvider, FT_METADATA_SPEC
|
||||
};
|
||||
|
||||
use near_contract_standards::fungible_token::FungibleToken;
|
||||
use near_sdk::collections::LazyOption;
|
||||
use near_sdk::json_types::{U128};
|
||||
|
||||
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
|
||||
use near_sdk::{
|
||||
env, near_bindgen, AccountId, PanicOnDefault,
|
||||
PromiseOrValue, StorageUsage,
|
||||
};
|
||||
|
||||
#[near_bindgen]
|
||||
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
|
||||
pub struct MockFTContract {
|
||||
token: FungibleToken,
|
||||
meta: LazyOption<FungibleTokenMetadata>,
|
||||
controller: AccountId
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
impl MockFTContract {
|
||||
#[init]
|
||||
pub fn new() -> Self {
|
||||
assert!(!env::state_exists(), "Already initialized");
|
||||
|
||||
let name = "MockFT".to_string();
|
||||
|
||||
let metadata = FungibleTokenMetadata {
|
||||
spec: FT_METADATA_SPEC.to_string(),
|
||||
name: name.clone(),
|
||||
symbol: name,
|
||||
icon: Some("".to_string()), // Is there ANY way to supply this?
|
||||
reference: None,
|
||||
reference_hash: None,
|
||||
decimals: 9,
|
||||
};
|
||||
|
||||
Self {
|
||||
token: FungibleToken::new(b"ft".to_vec()),
|
||||
meta: LazyOption::new(b"md".to_vec(), Some(&metadata)),
|
||||
controller: env::predecessor_account_id(),
|
||||
}
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn airdrop(&mut self, a: AccountId, amount: u128) {
|
||||
self.storage_deposit(Some(a.clone()), None);
|
||||
self.token.internal_deposit(&a, amount);
|
||||
|
||||
near_contract_standards::fungible_token::events::FtMint {
|
||||
owner_id: &a,
|
||||
amount: &U128::from(amount),
|
||||
memo: Some("wormhole mock minted tokens"),
|
||||
}
|
||||
.emit();
|
||||
}
|
||||
|
||||
pub fn account_storage_usage(&self) -> StorageUsage {
|
||||
self.token.account_storage_usage
|
||||
}
|
||||
}
|
||||
|
||||
near_contract_standards::impl_fungible_token_core!(MockFTContract, token);
|
||||
near_contract_standards::impl_fungible_token_storage!(MockFTContract, token);
|
||||
|
||||
#[near_bindgen]
|
||||
impl FungibleTokenMetadataProvider for MockFTContract {
|
||||
fn ft_metadata(&self) -> FungibleTokenMetadata {
|
||||
self.meta.get().unwrap()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "near-nft-bridge"
|
||||
version = "0.1.0"
|
||||
authors = ["Josh Siegel <jsiegel@jumptrading.com>"]
|
||||
edition = "2022"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
serde_json = "*"
|
||||
near-sdk = { version = "4.0.0", features = ["unstable"] }
|
||||
near-contract-standards = { version = "4.0.0" }
|
||||
hex = { version = "0.4.3" }
|
||||
near-sys = { version = "0.2.0" }
|
||||
bs58 = "*"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
debug = false
|
||||
panic = "abort"
|
||||
# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
|
||||
overflow-checks = true
|
||||
|
||||
[workspace]
|
||||
members = []
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
../target/wasm32-unknown-unknown/release/near_nft_bridge.wasm: *.rs ../Cargo.toml
|
||||
(cd ..; cargo build --target wasm32-unknown-unknown --release)
|
||||
|
||||
.PHONY: clippy
|
||||
clippy:
|
||||
(cd ..; cargo clippy)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../wormhole/src/byte_utils.rs
|
|
@ -0,0 +1,953 @@
|
|||
#![allow(unused_mut)]
|
||||
//#![allow(unused_imports)]
|
||||
//#![allow(unused_variables)]
|
||||
//#![allow(dead_code)]
|
||||
|
||||
use {
|
||||
near_contract_standards::non_fungible_token::{
|
||||
metadata::{
|
||||
NFTContractMetadata,
|
||||
TokenMetadata,
|
||||
NFT_METADATA_SPEC,
|
||||
},
|
||||
Token,
|
||||
TokenId,
|
||||
},
|
||||
near_sdk::{
|
||||
borsh::{
|
||||
self,
|
||||
BorshDeserialize,
|
||||
BorshSerialize,
|
||||
},
|
||||
collections::{
|
||||
LookupMap,
|
||||
UnorderedSet,
|
||||
},
|
||||
env,
|
||||
ext_contract,
|
||||
json_types::{
|
||||
Base64VecU8,
|
||||
},
|
||||
near_bindgen,
|
||||
utils::{
|
||||
assert_one_yocto,
|
||||
is_promise_success,
|
||||
},
|
||||
AccountId,
|
||||
Balance,
|
||||
Gas,
|
||||
PanicOnDefault,
|
||||
Promise,
|
||||
PromiseError,
|
||||
PromiseOrValue,
|
||||
PublicKey,
|
||||
},
|
||||
std::str,
|
||||
};
|
||||
|
||||
pub mod byte_utils;
|
||||
pub mod state;
|
||||
|
||||
use crate::byte_utils::{
|
||||
get_string_from_32,
|
||||
ByteUtils,
|
||||
};
|
||||
|
||||
const CHAIN_ID_NEAR: u16 = 15;
|
||||
const CHAIN_ID_SOL: u16 = 1;
|
||||
|
||||
const BRIDGE_NFT_BINARY: &[u8] =
|
||||
include_bytes!("../../nft-wrapped/target/wasm32-unknown-unknown/release/near_nft.wasm");
|
||||
|
||||
/// Initial balance for the BridgeToken contract to cover storage and related.
|
||||
const TRANSFER_BUFFER: u128 = 2000;
|
||||
|
||||
#[ext_contract(ext_nft_contract)]
|
||||
pub trait NFTContract {
|
||||
fn new(owner_id: AccountId, metadata: NFTContractMetadata, seq_number: u64) -> Self;
|
||||
fn nft_transfer(
|
||||
&mut self,
|
||||
receiver_id: AccountId,
|
||||
token_id: TokenId,
|
||||
approval_id: Option<u64>,
|
||||
memo: Option<String>,
|
||||
);
|
||||
|
||||
fn update_ft(&mut self, owner_id: AccountId, metadata: NFTContractMetadata, seq_number: u64);
|
||||
fn nft_token(&self, token_id: TokenId) -> Option<Token>;
|
||||
fn nft_mint(
|
||||
&mut self,
|
||||
token_id: TokenId,
|
||||
token_owner_id: AccountId,
|
||||
token_metadata: TokenMetadata,
|
||||
refund_to: AccountId,
|
||||
) -> Token;
|
||||
fn nft_burn(&mut self, token_id: TokenId, from: AccountId, refund_to: AccountId) -> Promise;
|
||||
}
|
||||
|
||||
#[ext_contract(ext_token_bridge)]
|
||||
pub trait ExtTokenBridge {
|
||||
fn finish_deploy(&self, token: String, token_id: String);
|
||||
}
|
||||
|
||||
#[ext_contract(ext_worm_hole)]
|
||||
pub trait Wormhole {
|
||||
fn verify_vaa(&self, vaa: String) -> u32;
|
||||
fn publish_message(&self, data: String, nonce: u32) -> u64;
|
||||
}
|
||||
|
||||
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
|
||||
pub struct TokenData {
|
||||
meta: String,
|
||||
address: String,
|
||||
chain: u16,
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
#[derive(BorshDeserialize, BorshSerialize)]
|
||||
pub struct NFTBridgeOld {
|
||||
booted: bool,
|
||||
core: AccountId,
|
||||
dups: UnorderedSet<Vec<u8>>,
|
||||
owner_pk: PublicKey,
|
||||
emitter_registration: LookupMap<u16, Vec<u8>>,
|
||||
last_asset: u32,
|
||||
upgrade_hash: Vec<u8>,
|
||||
|
||||
tokens: LookupMap<AccountId, TokenData>,
|
||||
key_map: LookupMap<Vec<u8>, AccountId>,
|
||||
hash_map: LookupMap<Vec<u8>, AccountId>,
|
||||
token_map: LookupMap<Vec<u8>, String>,
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
#[derive(BorshDeserialize, BorshSerialize)]
|
||||
pub struct NFTBridge {
|
||||
booted: bool,
|
||||
core: AccountId,
|
||||
gov_idx: u32,
|
||||
dups: LookupMap<Vec<u8>, bool>,
|
||||
owner_pk: PublicKey,
|
||||
emitter_registration: LookupMap<u16, Vec<u8>>,
|
||||
last_asset: u32,
|
||||
upgrade_hash: Vec<u8>,
|
||||
|
||||
tokens: LookupMap<AccountId, TokenData>,
|
||||
key_map: LookupMap<Vec<u8>, AccountId>,
|
||||
hash_map: LookupMap<Vec<u8>, AccountId>,
|
||||
token_map: LookupMap<Vec<u8>, String>,
|
||||
|
||||
bank: LookupMap<AccountId, Balance>,
|
||||
}
|
||||
|
||||
impl Default for NFTBridge {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
booted: false,
|
||||
core: AccountId::new_unchecked("".to_string()),
|
||||
gov_idx: 0,
|
||||
dups: LookupMap::new(b"d".to_vec()),
|
||||
owner_pk: env::signer_account_pk(),
|
||||
emitter_registration: LookupMap::new(b"c".to_vec()),
|
||||
last_asset: 0,
|
||||
upgrade_hash: b"".to_vec(),
|
||||
|
||||
tokens: LookupMap::new(b"ta".to_vec()),
|
||||
key_map: LookupMap::new(b"k".to_vec()),
|
||||
hash_map: LookupMap::new(b"a".to_vec()),
|
||||
token_map: LookupMap::new(b"tm".to_vec()),
|
||||
|
||||
bank: LookupMap::new(b"b".to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vaa_register_chain(
|
||||
storage: &mut NFTBridge,
|
||||
vaa: &state::ParsedVAA,
|
||||
mut deposit: Balance,
|
||||
refund_to: &AccountId,
|
||||
) -> Balance {
|
||||
let data: &[u8] = &vaa.payload;
|
||||
let target_chain = data.get_u16(33);
|
||||
let chain = data.get_u16(35);
|
||||
|
||||
if (target_chain != CHAIN_ID_NEAR) && (target_chain != 0) {
|
||||
refund_and_panic("InvalidREegisterChainChain", refund_to);
|
||||
}
|
||||
|
||||
if storage.emitter_registration.contains_key(&chain) {
|
||||
refund_and_panic("DuplicateChainRegistration", refund_to);
|
||||
}
|
||||
|
||||
let storage_used = env::storage_usage();
|
||||
storage
|
||||
.emitter_registration
|
||||
.insert(&chain, &data[37..69].to_vec());
|
||||
let required_cost = (Balance::from(env::storage_usage()) - Balance::from(storage_used))
|
||||
* env::storage_byte_cost();
|
||||
|
||||
if required_cost > deposit {
|
||||
refund_and_panic("DepositUnderflowForRegistration", refund_to);
|
||||
}
|
||||
deposit -= required_cost;
|
||||
|
||||
env::log_str(&format!(
|
||||
"register chain {} to {}",
|
||||
chain,
|
||||
hex::encode(&data[37..69])
|
||||
));
|
||||
|
||||
deposit
|
||||
}
|
||||
|
||||
fn vaa_upgrade_contract(
|
||||
storage: &mut NFTBridge,
|
||||
vaa: &state::ParsedVAA,
|
||||
deposit: Balance,
|
||||
refund_to: &AccountId,
|
||||
) -> Balance {
|
||||
let data: &[u8] = &vaa.payload;
|
||||
let chain = data.get_u16(33);
|
||||
if chain != CHAIN_ID_NEAR {
|
||||
refund_and_panic("InvalidContractUpgradeChain", refund_to);
|
||||
}
|
||||
|
||||
let uh = data.get_bytes32(0);
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: vaa_update_contract: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
hex::encode(&uh)
|
||||
));
|
||||
storage.upgrade_hash = uh.to_vec(); // Too lazy to do proper accounting here...
|
||||
deposit
|
||||
}
|
||||
|
||||
fn vaa_governance(
|
||||
storage: &mut NFTBridge,
|
||||
vaa: &state::ParsedVAA,
|
||||
gov_idx: u32,
|
||||
deposit: Balance,
|
||||
refund_to: &AccountId,
|
||||
) -> Balance {
|
||||
if gov_idx != vaa.guardian_set_index {
|
||||
refund_and_panic("InvalidGovernanceSet", refund_to);
|
||||
}
|
||||
|
||||
if (CHAIN_ID_SOL != vaa.emitter_chain)
|
||||
|| (hex::decode("0000000000000000000000000000000000000000000000000000000000000004")
|
||||
.unwrap()
|
||||
!= vaa.emitter_address)
|
||||
{
|
||||
refund_and_panic("InvalidGovernanceEmitter", refund_to);
|
||||
}
|
||||
|
||||
let data: &[u8] = &vaa.payload;
|
||||
let action = data.get_u8(32);
|
||||
|
||||
match action {
|
||||
1u8 => vaa_register_chain(storage, vaa, deposit, refund_to),
|
||||
2u8 => vaa_upgrade_contract(storage, vaa, deposit, refund_to),
|
||||
_ => refund_and_panic("InvalidGovernanceAction", refund_to),
|
||||
}
|
||||
}
|
||||
|
||||
fn vaa_transfer(
|
||||
storage: &mut NFTBridge,
|
||||
vaa: &state::ParsedVAA,
|
||||
_action: u8,
|
||||
mut deposit: Balance,
|
||||
refund_to: AccountId,
|
||||
) -> PromiseOrValue<bool> {
|
||||
let data: &[u8] = &vaa.payload;
|
||||
|
||||
let mut offset: usize = 1; // offset into data in bytes
|
||||
let nft_address = data.get_const_bytes::<32>(offset).to_vec();
|
||||
offset += 32;
|
||||
let nft_chain = data.get_u16(offset);
|
||||
offset += 2;
|
||||
let symbol = data.get_const_bytes::<32>(offset);
|
||||
offset += 32;
|
||||
let name = data.get_const_bytes::<32>(offset);
|
||||
offset += 32;
|
||||
|
||||
let token_id_vec = data.get_const_bytes::<32>(offset).to_vec();
|
||||
let token_id = hex::encode(token_id_vec.clone());
|
||||
|
||||
offset += 32;
|
||||
let uri_length: usize = data.get_u8(offset).into();
|
||||
offset += 1;
|
||||
let uri = data.get_bytes(offset, uri_length).to_vec();
|
||||
offset += uri_length;
|
||||
let recipient = data.get_const_bytes::<32>(offset).to_vec();
|
||||
offset += 32;
|
||||
let recipient_chain = data.get_u16(offset);
|
||||
|
||||
if recipient_chain != CHAIN_ID_NEAR {
|
||||
refund_and_panic("Not directed at this chain", &refund_to);
|
||||
}
|
||||
|
||||
if !storage.hash_map.contains_key(&recipient) {
|
||||
refund_and_panic("ReceipientNotRegistered", &refund_to);
|
||||
}
|
||||
|
||||
let recipient_account = storage.hash_map.get(&recipient).unwrap();
|
||||
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
hex::encode(recipient)
|
||||
));
|
||||
|
||||
let bridge_token_account;
|
||||
|
||||
let mut prom = if nft_chain == CHAIN_ID_NEAR {
|
||||
if !storage.hash_map.contains_key(&nft_address) {
|
||||
refund_and_panic("ReceipientNotRegistered", &refund_to);
|
||||
}
|
||||
|
||||
deposit -= 1;
|
||||
|
||||
let bridge_token_account_id = storage.hash_map.get(&nft_address).unwrap();
|
||||
bridge_token_account = bridge_token_account_id.to_string();
|
||||
|
||||
ext_nft_contract::ext(bridge_token_account_id)
|
||||
.with_attached_deposit(1)
|
||||
.nft_transfer(recipient_account, token_id.clone(), None, None)
|
||||
} else {
|
||||
// The land of Wormhole assets
|
||||
let tkey = nft_key(nft_address.clone(), nft_chain);
|
||||
|
||||
let base_uri = String::from_utf8(uri).unwrap();
|
||||
|
||||
let reference = hex::encode(&vaa.payload);
|
||||
|
||||
let storage_used = env::storage_usage();
|
||||
let token_key = [tkey.clone(), token_id_vec].concat();
|
||||
storage.token_map.insert(&token_key, &reference);
|
||||
|
||||
let storage_used_now = env::storage_usage();
|
||||
let delta = (storage_used_now - storage_used) as u128 * env::storage_byte_cost();
|
||||
|
||||
if delta > deposit {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: vaa_trnsfer: delta: {} bytes: {} needed: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
delta,
|
||||
storage_used_now - storage_used,
|
||||
((deposit - delta) / env::storage_byte_cost())
|
||||
));
|
||||
refund_and_panic("PrecheckFailedDepositUnderFlow", &refund_to);
|
||||
}
|
||||
deposit -= delta;
|
||||
|
||||
let n = get_string_from_32(&name);
|
||||
let s = get_string_from_32(&symbol);
|
||||
|
||||
let md = TokenMetadata {
|
||||
title: Some(n.clone()),
|
||||
description: Some(s.clone()),
|
||||
media: Some(base_uri.clone()),
|
||||
media_hash: Some(Base64VecU8::from(env::sha256(base_uri.as_bytes()))),
|
||||
copies: Some(1u64),
|
||||
issued_at: None,
|
||||
expires_at: None,
|
||||
starts_at: None,
|
||||
updated_at: None,
|
||||
extra: None,
|
||||
reference: None,
|
||||
reference_hash: None,
|
||||
};
|
||||
|
||||
if storage.key_map.contains_key(&tkey) {
|
||||
let dep = deposit;
|
||||
deposit = 0;
|
||||
|
||||
let bridge_token_account_id = storage.key_map.get(&tkey).unwrap();
|
||||
bridge_token_account = bridge_token_account_id.to_string();
|
||||
|
||||
ext_nft_contract::ext(bridge_token_account_id)
|
||||
.with_attached_deposit(dep)
|
||||
.nft_mint(token_id.clone(), recipient_account, md, refund_to.clone())
|
||||
} else {
|
||||
let ft = NFTContractMetadata {
|
||||
spec: NFT_METADATA_SPEC.to_string(),
|
||||
name: n.clone() + " (wormhole)",
|
||||
symbol: s.clone(),
|
||||
icon: None,
|
||||
base_uri: None,
|
||||
reference: None,
|
||||
reference_hash: None,
|
||||
};
|
||||
|
||||
let storage_used = env::storage_usage();
|
||||
storage.last_asset += 1;
|
||||
let asset_id = storage.last_asset;
|
||||
bridge_token_account = format!("{}.{}", asset_id, env::current_account_id());
|
||||
let bridge_token_account_id: AccountId =
|
||||
AccountId::new_unchecked(bridge_token_account.clone());
|
||||
|
||||
let d = TokenData {
|
||||
meta: reference,
|
||||
address: hex::encode(nft_address),
|
||||
chain: nft_chain,
|
||||
};
|
||||
|
||||
storage.tokens.insert(&bridge_token_account_id, &d);
|
||||
storage.key_map.insert(&tkey, &bridge_token_account_id);
|
||||
storage.hash_map.insert(
|
||||
&env::sha256(bridge_token_account.as_bytes()),
|
||||
&bridge_token_account_id,
|
||||
);
|
||||
|
||||
let storage_used_now = env::storage_usage();
|
||||
|
||||
let delta = (storage_used_now - storage_used) as u128 * env::storage_byte_cost();
|
||||
|
||||
let cost = ((TRANSFER_BUFFER * 2) + BRIDGE_NFT_BINARY.len() as u128)
|
||||
* env::storage_byte_cost();
|
||||
if cost + delta > deposit {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: vaa_trnsfer: cost: {} delta: {} bytes: {} needed: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
cost,
|
||||
delta,
|
||||
storage_used_now - storage_used,
|
||||
((deposit - (cost + delta)) / env::storage_byte_cost())
|
||||
));
|
||||
refund_and_panic("PrecheckFailedDepositUnderFlow", &refund_to);
|
||||
}
|
||||
|
||||
deposit -= cost + delta;
|
||||
|
||||
let dep = deposit;
|
||||
deposit = 0;
|
||||
|
||||
Promise::new(bridge_token_account_id.clone())
|
||||
.create_account()
|
||||
.transfer(cost)
|
||||
.add_full_access_key(storage.owner_pk.clone())
|
||||
.deploy_contract(BRIDGE_NFT_BINARY.to_vec())
|
||||
// Lets initialize it with useful stuff
|
||||
.then(ext_nft_contract::ext(bridge_token_account_id.clone()).new(
|
||||
env::current_account_id(),
|
||||
ft,
|
||||
vaa.sequence,
|
||||
))
|
||||
.then(
|
||||
ext_nft_contract::ext(bridge_token_account_id)
|
||||
.with_attached_deposit(dep)
|
||||
.nft_mint(token_id.clone(), recipient_account, md, refund_to.clone()),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if deposit > 0 {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: refund {} to {}",
|
||||
file!(),
|
||||
line!(),
|
||||
deposit,
|
||||
env::predecessor_account_id()
|
||||
));
|
||||
prom = prom.then(Promise::new(refund_to).transfer(deposit));
|
||||
}
|
||||
|
||||
PromiseOrValue::Promise(prom.then(
|
||||
ext_token_bridge::ext(env::current_account_id()).finish_deploy(bridge_token_account, token_id),
|
||||
))
|
||||
}
|
||||
|
||||
fn refund_and_panic(s: &str, refund_to: &AccountId) -> ! {
|
||||
if env::attached_deposit() > 0 {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: refund {} to {}",
|
||||
file!(),
|
||||
line!(),
|
||||
env::attached_deposit(),
|
||||
refund_to
|
||||
));
|
||||
Promise::new(refund_to.clone()).transfer(env::attached_deposit());
|
||||
}
|
||||
env::panic_str(s);
|
||||
}
|
||||
|
||||
fn nft_key(address: Vec<u8>, chain: u16) -> Vec<u8> {
|
||||
[address, chain.to_be_bytes().to_vec()].concat()
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
impl NFTBridge {
|
||||
pub fn emitter(&self) -> (String, String) {
|
||||
let acct = env::current_account_id();
|
||||
let astr = acct.to_string();
|
||||
|
||||
(astr.clone(), hex::encode(env::sha256(astr.as_bytes())))
|
||||
}
|
||||
|
||||
pub fn is_wormhole(&self, token: &String) -> bool {
|
||||
let astr = format!(".{}", env::current_account_id().as_str());
|
||||
token.ends_with(&astr)
|
||||
}
|
||||
|
||||
pub fn deposit_estimates(&self) -> (String, String) {
|
||||
// This is a worst case if we have to store a lot of data as well as create a new account
|
||||
let cost =
|
||||
((TRANSFER_BUFFER * 5) + BRIDGE_NFT_BINARY.len() as u128) * env::storage_byte_cost();
|
||||
|
||||
(env::storage_byte_cost().to_string(), cost.to_string())
|
||||
}
|
||||
|
||||
pub fn get_original_asset(&self, token: String) -> (String, u16) {
|
||||
let account = AccountId::new_unchecked(token);
|
||||
|
||||
if !self.tokens.contains_key(&account) {
|
||||
env::panic_str("UnknownAssetId");
|
||||
}
|
||||
|
||||
let t = self.tokens.get(&account).unwrap();
|
||||
(t.address, t.chain)
|
||||
}
|
||||
|
||||
pub fn get_foreign_asset(&self, address: String, chain: u16) -> String {
|
||||
let p = nft_key(hex::decode(address).unwrap(), chain);
|
||||
|
||||
if self.key_map.contains_key(&p) {
|
||||
return self.key_map.get(&p).unwrap().to_string();
|
||||
}
|
||||
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn register_account(&mut self, account: String) -> String {
|
||||
let old_storage_cost = env::storage_usage() as u128 * env::storage_byte_cost() as u128;
|
||||
|
||||
let account_hash = env::sha256(account.as_bytes());
|
||||
let ret = hex::encode(&account_hash);
|
||||
|
||||
if self.hash_map.contains_key(&account_hash) {
|
||||
Promise::new(env::predecessor_account_id()).transfer(env::attached_deposit());
|
||||
return ret;
|
||||
}
|
||||
let a = AccountId::new_unchecked(account);
|
||||
self.hash_map.insert(&account_hash, &a);
|
||||
|
||||
let new_storage_cost = env::storage_usage() as u128 * env::storage_byte_cost() as u128;
|
||||
if new_storage_cost <= old_storage_cost {
|
||||
refund_and_panic("ImpossibleStorageCost", &env::predecessor_account_id());
|
||||
}
|
||||
|
||||
if (new_storage_cost - old_storage_cost) > env::attached_deposit() {
|
||||
refund_and_panic("InvalidStorageDeposit", &env::predecessor_account_id());
|
||||
}
|
||||
|
||||
let refund = env::attached_deposit() - (new_storage_cost - old_storage_cost);
|
||||
if refund > 0 {
|
||||
Promise::new(env::predecessor_account_id()).transfer(refund);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn hash_account(&self, account: String) -> (bool, String) {
|
||||
// Yes, you could hash it yourself but then you wouldn't know
|
||||
// if it was already registered...
|
||||
let account_hash = env::sha256(account.as_bytes());
|
||||
let ret = hex::encode(&account_hash);
|
||||
(self.hash_map.contains_key(&account_hash), ret)
|
||||
}
|
||||
|
||||
pub fn hash_lookup(&self, hash: String) -> (bool, String) {
|
||||
let account_hash = hex::decode(&hash).unwrap();
|
||||
if self.hash_map.contains_key(&account_hash) {
|
||||
(true, self.hash_map.get(&account_hash).unwrap().to_string())
|
||||
} else {
|
||||
(false, "".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_transfer_completed(&self, vaa: String) -> (bool, bool) {
|
||||
let h = hex::decode(vaa).expect("invalidVaa");
|
||||
let pvaa = state::ParsedVAA::parse(&h);
|
||||
|
||||
if self.dups.contains_key(&pvaa.hash) {
|
||||
(true, self.dups.get(&pvaa.hash).unwrap())
|
||||
} else {
|
||||
(false, false)
|
||||
}
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn submit_vaa(
|
||||
&mut self,
|
||||
vaa: String,
|
||||
mut refund_to: Option<AccountId>,
|
||||
) -> PromiseOrValue<bool> {
|
||||
if refund_to == None {
|
||||
refund_to = Some(env::predecessor_account_id());
|
||||
}
|
||||
|
||||
if env::prepaid_gas() < Gas(300_000_000_000_000) {
|
||||
env::panic_str("NotEnoughGas");
|
||||
}
|
||||
|
||||
if env::attached_deposit() < (TRANSFER_BUFFER * env::storage_byte_cost()) {
|
||||
env::panic_str("StorageDepositUnderflow");
|
||||
}
|
||||
|
||||
let h = hex::decode(&vaa).unwrap();
|
||||
let pvaa = state::ParsedVAA::parse(&h);
|
||||
|
||||
if pvaa.version != 1 {
|
||||
env::panic_str("invalidVersion");
|
||||
}
|
||||
|
||||
// Check if VAA with this hash was already accepted
|
||||
if self.dups.contains_key(&pvaa.hash) {
|
||||
let e = self.dups.get(&pvaa.hash).unwrap();
|
||||
if e {
|
||||
env::panic_str("alreadyExecuted");
|
||||
} else {
|
||||
self.dups.insert(&pvaa.hash, &true);
|
||||
self.submit_vaa_work(&pvaa, refund_to.unwrap())
|
||||
}
|
||||
} else {
|
||||
let r = refund_to.unwrap();
|
||||
PromiseOrValue::Promise(
|
||||
ext_worm_hole::ext(self.core.clone())
|
||||
.verify_vaa(vaa.clone())
|
||||
.then(
|
||||
Self::ext(env::current_account_id())
|
||||
.with_unused_gas_weight(10)
|
||||
.with_attached_deposit(env::attached_deposit())
|
||||
.verify_vaa_callback(pvaa.hash, r.clone()),
|
||||
)
|
||||
.then(
|
||||
Self::ext(env::current_account_id()).refunder(r, env::attached_deposit()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[private]
|
||||
pub fn refunder(&mut self, refund_to: AccountId, amt: Balance) {
|
||||
if !is_promise_success() {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: refunding {} to {}?",
|
||||
file!(),
|
||||
line!(),
|
||||
amt,
|
||||
refund_to
|
||||
));
|
||||
Promise::new(refund_to).transfer(amt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[private] // So, all of wormhole security rests in this one statement?
|
||||
#[payable]
|
||||
pub fn verify_vaa_callback(
|
||||
&mut self,
|
||||
hash: Vec<u8>,
|
||||
refund_to: AccountId,
|
||||
#[callback_result] gov_idx: Result<u32, PromiseError>,
|
||||
) -> Promise {
|
||||
if gov_idx.is_err() {
|
||||
env::panic_str("vaaVerifyFail");
|
||||
}
|
||||
self.gov_idx = gov_idx.unwrap();
|
||||
|
||||
// Check if VAA with this hash was already accepted
|
||||
if self.dups.contains_key(&hash) {
|
||||
env::panic_str("alreadyExecuted2");
|
||||
}
|
||||
|
||||
let storage_used = env::storage_usage();
|
||||
let mut deposit = env::attached_deposit();
|
||||
|
||||
self.dups.insert(&hash, &false);
|
||||
|
||||
let required_cost =
|
||||
(Balance::from(env::storage_usage() - storage_used)) * env::storage_byte_cost();
|
||||
if required_cost > deposit {
|
||||
env::panic_str("DepositUnderflowForHash");
|
||||
}
|
||||
deposit -= required_cost;
|
||||
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: refunding {} to {}?",
|
||||
file!(),
|
||||
line!(),
|
||||
deposit,
|
||||
refund_to
|
||||
));
|
||||
Promise::new(refund_to).transfer(deposit)
|
||||
}
|
||||
|
||||
#[private] // So, all of wormhole security rests in this one statement?
|
||||
#[payable]
|
||||
fn submit_vaa_work(
|
||||
&mut self,
|
||||
pvaa: &state::ParsedVAA,
|
||||
refund_to: AccountId,
|
||||
) -> PromiseOrValue<bool> {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: submit_vaa_callback: {} {} used: {} prepaid: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
env::attached_deposit(),
|
||||
env::predecessor_account_id(),
|
||||
serde_json::to_string(&env::used_gas()).unwrap(),
|
||||
serde_json::to_string(&env::prepaid_gas()).unwrap()
|
||||
));
|
||||
|
||||
if pvaa.version != 1 {
|
||||
env::panic_str("invalidVersion");
|
||||
}
|
||||
|
||||
let data: &[u8] = &pvaa.payload;
|
||||
|
||||
let governance = data[0..32]
|
||||
== hex::decode("00000000000000000000000000000000000000000000004e4654427269646765")
|
||||
.unwrap();
|
||||
let action = data.get_u8(0);
|
||||
|
||||
let deposit = env::attached_deposit();
|
||||
|
||||
if governance {
|
||||
let bal = vaa_governance(self, pvaa, self.gov_idx, deposit, &refund_to);
|
||||
if bal > 0 {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: refunding {} to {}",
|
||||
file!(),
|
||||
line!(),
|
||||
bal,
|
||||
refund_to
|
||||
));
|
||||
|
||||
return PromiseOrValue::Promise(Promise::new(refund_to).transfer(bal));
|
||||
}
|
||||
return PromiseOrValue::Value(true);
|
||||
}
|
||||
|
||||
if !self.emitter_registration.contains_key(&pvaa.emitter_chain) {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: Chain Not Registered: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
pvaa.emitter_chain
|
||||
));
|
||||
|
||||
refund_and_panic("ChainNotRegistered", &refund_to);
|
||||
}
|
||||
|
||||
let ce = self.emitter_registration.get(&pvaa.emitter_chain).unwrap();
|
||||
if ce != pvaa.emitter_address {
|
||||
refund_and_panic("InvalidRegistration", &refund_to);
|
||||
}
|
||||
|
||||
if action == 1u8 {
|
||||
vaa_transfer(self, &pvaa, action, deposit, refund_to)
|
||||
} else {
|
||||
refund_and_panic("invalidPortAction", &refund_to);
|
||||
}
|
||||
}
|
||||
|
||||
#[private]
|
||||
pub fn finish_deploy(&mut self, token: String, token_id: String) -> (String, String) {
|
||||
if is_promise_success() {
|
||||
(token, token_id)
|
||||
} else {
|
||||
env::panic_str("bad deploy");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boot_portal(&mut self, core: String) {
|
||||
if self.owner_pk != env::signer_account_pk() {
|
||||
env::panic_str("invalidSigner");
|
||||
}
|
||||
|
||||
if self.booted {
|
||||
env::panic_str("NoDonut");
|
||||
}
|
||||
self.booted = true;
|
||||
self.core = AccountId::try_from(core).unwrap();
|
||||
|
||||
let account_hash = env::sha256(env::current_account_id().to_string().as_bytes());
|
||||
env::log_str(&format!("nft emitter: {}", hex::encode(account_hash)));
|
||||
}
|
||||
|
||||
#[private]
|
||||
pub fn update_contract_done(
|
||||
&mut self,
|
||||
refund_to: near_sdk::AccountId,
|
||||
storage_used: u64,
|
||||
attached_deposit: u128,
|
||||
) {
|
||||
let delta = (env::storage_usage() as i128 - storage_used as i128)
|
||||
* env::storage_byte_cost() as i128;
|
||||
let refund = attached_deposit as i128 - delta;
|
||||
if refund > 0 {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: update_contract_done: refund {} to {}",
|
||||
file!(),
|
||||
line!(),
|
||||
refund,
|
||||
refund_to
|
||||
));
|
||||
Promise::new(refund_to).transfer(refund as u128);
|
||||
}
|
||||
}
|
||||
|
||||
#[private]
|
||||
fn update_contract_work(&mut self, v: Vec<u8>) -> Promise {
|
||||
if env::attached_deposit() == 0 {
|
||||
env::panic_str("attach some cash");
|
||||
}
|
||||
|
||||
let s = env::sha256(&v);
|
||||
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: update_contract: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
hex::encode(&s)
|
||||
));
|
||||
|
||||
if s.to_vec() != self.upgrade_hash {
|
||||
if env::attached_deposit() > 0 {
|
||||
env::log_str(&format!(
|
||||
"nft-bridge/{}#{}: refunding {} to {}",
|
||||
file!(),
|
||||
line!(),
|
||||
env::attached_deposit(),
|
||||
env::predecessor_account_id()
|
||||
));
|
||||
|
||||
Promise::new(env::predecessor_account_id()).transfer(env::attached_deposit());
|
||||
}
|
||||
env::panic_str("invalidUpgradeContract");
|
||||
}
|
||||
|
||||
Promise::new(env::current_account_id())
|
||||
.deploy_contract(v.to_vec())
|
||||
.then(Self::ext(env::current_account_id()).update_contract_done(
|
||||
env::predecessor_account_id(),
|
||||
env::storage_usage(),
|
||||
env::attached_deposit(),
|
||||
))
|
||||
}
|
||||
|
||||
//#[allow(clippy::too_many_arguments)]
|
||||
#[payable]
|
||||
pub fn initiate_transfer(
|
||||
&mut self,
|
||||
asset: AccountId,
|
||||
token_id: TokenId,
|
||||
recipient_chain: u16,
|
||||
recipient: String,
|
||||
nonce: u32,
|
||||
) -> Promise {
|
||||
assert_one_yocto();
|
||||
|
||||
if env::prepaid_gas() < Gas(300_000_000_000_000) {
|
||||
refund_and_panic("NotEnoughGas", &env::predecessor_account_id());
|
||||
}
|
||||
|
||||
if !self.tokens.contains_key(&asset) {
|
||||
refund_and_panic("UnknownWormholeAsset", &env::predecessor_account_id());
|
||||
}
|
||||
|
||||
let td = self.tokens.get(&asset).unwrap();
|
||||
|
||||
let token_key = [
|
||||
hex::decode(td.address).unwrap(),
|
||||
td.chain.to_be_bytes().to_vec(),
|
||||
hex::decode(&token_id).unwrap(),
|
||||
]
|
||||
.concat();
|
||||
if !self.token_map.contains_key(&token_key) {
|
||||
refund_and_panic("CannotFindMetaDataForToken", &env::predecessor_account_id());
|
||||
}
|
||||
|
||||
let meta = self.token_map.get(&token_key).unwrap();
|
||||
|
||||
let astr = format!(".{}", env::current_account_id().as_str());
|
||||
if asset.to_string().ends_with(&astr) {
|
||||
ext_nft_contract::ext(asset.clone())
|
||||
.nft_burn(
|
||||
token_id.clone(),
|
||||
env::predecessor_account_id(),
|
||||
env::predecessor_account_id(),
|
||||
)
|
||||
.then(
|
||||
Self::ext(env::current_account_id())
|
||||
.with_unused_gas_weight(10)
|
||||
.initiate_transfer_wormhole(
|
||||
asset,
|
||||
token_id,
|
||||
recipient_chain,
|
||||
recipient,
|
||||
nonce,
|
||||
meta,
|
||||
env::predecessor_account_id(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
refund_and_panic("NativeNFTsUseDifferentAPI", &env::predecessor_account_id());
|
||||
}
|
||||
}
|
||||
|
||||
#[private]
|
||||
pub fn initiate_transfer_wormhole(
|
||||
&mut self,
|
||||
_asset: AccountId,
|
||||
_token_id: TokenId,
|
||||
recipient_chain: u16,
|
||||
recipient: String,
|
||||
_nonce: u32,
|
||||
meta: String,
|
||||
_caller: AccountId,
|
||||
) -> Promise {
|
||||
if !is_promise_success() {
|
||||
env::panic_str("Failed to burn NFT");
|
||||
}
|
||||
|
||||
let old = hex::decode(meta).unwrap();
|
||||
|
||||
let p = [
|
||||
old[0..(old.len() - 34)].to_vec(),
|
||||
vec![0; (64 - recipient.len()) / 2],
|
||||
hex::decode(recipient).unwrap(),
|
||||
(recipient_chain as u16).to_be_bytes().to_vec(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
if old.len() != p.len() {
|
||||
refund_and_panic("formatting error", &env::predecessor_account_id());
|
||||
}
|
||||
|
||||
ext_worm_hole::ext(self.core.clone())
|
||||
.publish_message(hex::encode(p), env::block_height() as u32)
|
||||
}
|
||||
}
|
||||
|
||||
// let result = await userAccount.functionCall({
|
||||
// contractId: config.tokenAccount,
|
||||
// methodName: "update_contract",
|
||||
// args: wormholeContract,
|
||||
// attachedDeposit: "12500000000000000000000",
|
||||
// gas: 300000000000000,
|
||||
// });
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn update_contract() {
|
||||
env::setup_panic_hook();
|
||||
let mut contract: NFTBridge = env::state_read().expect("Contract is not initialized");
|
||||
contract.update_contract_work(env::input().unwrap());
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
../../wormhole/src/state.rs
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "near-nft"
|
||||
version = "0.1.0"
|
||||
authors = ["Josh Siegel <jsiegel@jumptrading.com>"]
|
||||
edition = "2022"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
near-sdk = { version = "4.0.0" }
|
||||
near-contract-standards = { version = "4.0.0" }
|
||||
hex = { version = "0.4.3" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
debug = false
|
||||
panic = "abort"
|
||||
# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
|
||||
overflow-checks = true
|
||||
|
||||
[workspace]
|
||||
members = []
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
../target/wasm32-unknown-unknown/release/near_ft.wasm: *.rs ../Cargo.toml
|
||||
(cd ..; cargo build --target wasm32-unknown-unknown --release)
|
||||
|
||||
.PHONY: clippy
|
||||
clippy:
|
||||
(cd ..; cargo clippy)
|
|
@ -0,0 +1,158 @@
|
|||
use near_contract_standards::non_fungible_token::metadata::{
|
||||
NFTContractMetadata, NonFungibleTokenMetadataProvider, TokenMetadata,
|
||||
};
|
||||
use near_contract_standards::non_fungible_token::NonFungibleToken;
|
||||
use near_contract_standards::non_fungible_token::{Token, TokenId};
|
||||
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
|
||||
use near_sdk::collections::LazyOption;
|
||||
use near_sdk::{
|
||||
env, near_bindgen, require, AccountId, BorshStorageKey, PanicOnDefault, Promise, PromiseOrValue,
|
||||
};
|
||||
|
||||
use near_contract_standards::non_fungible_token::events::{NftBurn, NftMint};
|
||||
|
||||
#[near_bindgen]
|
||||
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
|
||||
pub struct Contract {
|
||||
tokens: NonFungibleToken,
|
||||
metadata: LazyOption<NFTContractMetadata>,
|
||||
seq_number: u64,
|
||||
}
|
||||
|
||||
#[derive(BorshSerialize, BorshStorageKey)]
|
||||
enum StorageKey {
|
||||
NonFungibleToken,
|
||||
Metadata,
|
||||
TokenMetadata,
|
||||
Enumeration,
|
||||
Approval,
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
impl Contract {
|
||||
#[init]
|
||||
pub fn new(owner_id: AccountId, metadata: NFTContractMetadata, seq_number: u64) -> Self {
|
||||
require!(!env::state_exists(), "Already initialized");
|
||||
metadata.assert_valid();
|
||||
Self {
|
||||
tokens: NonFungibleToken::new(
|
||||
StorageKey::NonFungibleToken,
|
||||
owner_id,
|
||||
Some(StorageKey::TokenMetadata),
|
||||
Some(StorageKey::Enumeration),
|
||||
Some(StorageKey::Approval),
|
||||
),
|
||||
metadata: LazyOption::new(StorageKey::Metadata, Some(&metadata)),
|
||||
seq_number,
|
||||
}
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn nft_mint(
|
||||
&mut self,
|
||||
token_id: TokenId,
|
||||
token_owner_id: AccountId,
|
||||
token_metadata: TokenMetadata,
|
||||
refund_to: AccountId,
|
||||
) -> Token {
|
||||
assert_eq!(
|
||||
env::predecessor_account_id(),
|
||||
self.tokens.owner_id,
|
||||
"Unauthorized"
|
||||
);
|
||||
let token = self.tokens.internal_mint_with_refund(
|
||||
token_id,
|
||||
token_owner_id,
|
||||
Some(token_metadata),
|
||||
Some(refund_to),
|
||||
);
|
||||
|
||||
NftMint {
|
||||
owner_id: &token.owner_id,
|
||||
token_ids: &[&token.token_id],
|
||||
memo: Some("wormhole nft"),
|
||||
}
|
||||
.emit();
|
||||
|
||||
token
|
||||
}
|
||||
|
||||
pub fn nft_burn(
|
||||
&mut self,
|
||||
token_id: TokenId,
|
||||
from: AccountId,
|
||||
refund_to: AccountId,
|
||||
) -> Promise {
|
||||
assert_eq!(
|
||||
env::predecessor_account_id(),
|
||||
self.tokens.owner_id,
|
||||
"Unauthorized"
|
||||
);
|
||||
|
||||
let owner = self
|
||||
.tokens
|
||||
.owner_by_id
|
||||
.get(&token_id)
|
||||
.expect("unknown token id");
|
||||
|
||||
if owner != from {
|
||||
env::panic_str("owner is not who we expected it was")
|
||||
}
|
||||
|
||||
let storage_used = env::storage_usage();
|
||||
|
||||
// A lot of moving parts here.. code reviewers.. did I get it
|
||||
// all? Hard to believe nobody has implemented burn in the
|
||||
// standard SDK. Googling around found me some other NFT
|
||||
// contracts that tried to implement it but they didn't get
|
||||
// the storage management correct.
|
||||
|
||||
if let Some(tokens_per_owner) = &mut self.tokens.tokens_per_owner {
|
||||
// owner_tokens should always exist, so call `unwrap` without guard
|
||||
let mut owner_tokens = tokens_per_owner.get(&from).unwrap_or_else(|| {
|
||||
env::panic_str("Unable to access tokens per owner in unguarded call.")
|
||||
});
|
||||
owner_tokens.remove(&token_id);
|
||||
if owner_tokens.is_empty() {
|
||||
tokens_per_owner.remove(&from);
|
||||
} else {
|
||||
tokens_per_owner.insert(&from, &owner_tokens);
|
||||
}
|
||||
}
|
||||
|
||||
self.tokens.owner_by_id.remove(&token_id);
|
||||
|
||||
if let Some(next_approval_id_by_id) = &mut self.tokens.next_approval_id_by_id {
|
||||
next_approval_id_by_id.remove(&token_id);
|
||||
}
|
||||
if let Some(approvals_by_id) = &mut self.tokens.approvals_by_id {
|
||||
approvals_by_id.remove(&token_id);
|
||||
}
|
||||
if let Some(token_metadata_by_id) = &mut self.tokens.token_metadata_by_id {
|
||||
token_metadata_by_id.remove(&token_id);
|
||||
}
|
||||
|
||||
NftBurn {
|
||||
owner_id: &from,
|
||||
token_ids: &[&token_id],
|
||||
authorized_id: Some(&env::predecessor_account_id()),
|
||||
memo: Some("wormhole nft"),
|
||||
}
|
||||
.emit();
|
||||
|
||||
let storage_freed = storage_used - env::storage_usage();
|
||||
|
||||
Promise::new(refund_to).transfer(storage_freed as u128 * env::storage_byte_cost())
|
||||
}
|
||||
}
|
||||
|
||||
near_contract_standards::impl_non_fungible_token_core!(Contract, tokens);
|
||||
near_contract_standards::impl_non_fungible_token_approval!(Contract, tokens);
|
||||
near_contract_standards::impl_non_fungible_token_enumeration!(Contract, tokens);
|
||||
|
||||
#[near_bindgen]
|
||||
impl NonFungibleTokenMetadataProvider for Contract {
|
||||
fn nft_metadata(&self) -> NFTContractMetadata {
|
||||
self.metadata.get().unwrap()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "near-token-bridge"
|
||||
version = "0.1.0"
|
||||
authors = ["Josh Siegel <jsiegel@jumptrading.com>"]
|
||||
edition = "2022"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
serde_json = "*"
|
||||
near-sdk = { version = "4.0.0", features = ["unstable"] }
|
||||
near-contract-standards = { version = "4.0.0" }
|
||||
hex = { version = "0.4.3" }
|
||||
near-sys = { version = "0.2.0" }
|
||||
bs58 = "*"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
debug = false
|
||||
panic = "abort"
|
||||
# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
|
||||
overflow-checks = true
|
||||
|
||||
[workspace]
|
||||
members = []
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
../target/wasm32-unknown-unknown/release/near_token_bridge.wasm: *.rs ../Cargo.toml
|
||||
(cd ..; cargo build --target wasm32-unknown-unknown --release)
|
||||
|
||||
.PHONY: clippy
|
||||
clippy:
|
||||
(cd ..; cargo clippy)
|
||||
|
||||
.PHONY: clippy-fix
|
||||
clippy-fix:
|
||||
(cd ..; cargo clippy --fix)
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../wormhole/src/byte_utils.rs
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
../../wormhole/src/state.rs
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "near-wormhole"
|
||||
version = "0.1.0"
|
||||
authors = ["Josh Siegel <jsiegel@jumptrading.com>"]
|
||||
edition = "2022"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
serde_json = "*"
|
||||
near-sdk = { version = "4.0.0", features = ["unstable"] }
|
||||
hex = { version = "0.4.3" }
|
||||
bs58 = "*"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
debug = false
|
||||
panic = "abort"
|
||||
# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
|
||||
overflow-checks = true
|
||||
|
||||
[workspace]
|
||||
members = []
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
../target/wasm32-unknown-unknown/release/near_wormhole.wasm: *.rs ../Cargo.toml
|
||||
(cd ..; cargo build --target wasm32-unknown-unknown --release)
|
||||
|
||||
|
||||
.PHONY: clippy
|
||||
clippy:
|
||||
(cd ..; cargo clippy)
|
||||
|
||||
.PHONY: clippy-fix
|
||||
clippy-fix:
|
||||
(cd ..; cargo clippy --fix)
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
pub trait ByteUtils {
|
||||
fn get_u8(&self, index: usize) -> u8;
|
||||
fn get_u16(&self, index: usize) -> u16;
|
||||
fn get_u32(&self, index: usize) -> u32;
|
||||
fn get_u64(&self, index: usize) -> u64;
|
||||
|
||||
fn get_u128_be(&self, index: usize) -> u128;
|
||||
/// High 128 then low 128
|
||||
fn get_u256(&self, index: usize) -> (u128, u128);
|
||||
// fn get_address(&self, index: usize) -> CanonicalAddr;
|
||||
fn get_bytes32(&self, index: usize) -> &[u8];
|
||||
fn get_bytes(&self, index: usize, bytes: usize) -> &[u8];
|
||||
fn get_const_bytes<const N: usize>(&self, index: usize) -> [u8; N];
|
||||
}
|
||||
|
||||
impl ByteUtils for &[u8] {
|
||||
fn get_u8(&self, index: usize) -> u8 {
|
||||
self[index]
|
||||
}
|
||||
fn get_u16(&self, index: usize) -> u16 {
|
||||
let mut bytes: [u8; 16 / 8] = [0; 16 / 8];
|
||||
bytes.copy_from_slice(&self[index..index + 2]);
|
||||
u16::from_be_bytes(bytes)
|
||||
}
|
||||
fn get_u32(&self, index: usize) -> u32 {
|
||||
let mut bytes: [u8; 32 / 8] = [0; 32 / 8];
|
||||
bytes.copy_from_slice(&self[index..index + 4]);
|
||||
u32::from_be_bytes(bytes)
|
||||
}
|
||||
fn get_u64(&self, index: usize) -> u64 {
|
||||
let mut bytes: [u8; 64 / 8] = [0; 64 / 8];
|
||||
bytes.copy_from_slice(&self[index..index + 8]);
|
||||
u64::from_be_bytes(bytes)
|
||||
}
|
||||
fn get_u128_be(&self, index: usize) -> u128 {
|
||||
let mut bytes: [u8; 128 / 8] = [0; 128 / 8];
|
||||
bytes.copy_from_slice(&self[index..index + 128 / 8]);
|
||||
u128::from_be_bytes(bytes)
|
||||
}
|
||||
fn get_u256(&self, index: usize) -> (u128, u128) {
|
||||
(self.get_u128_be(index), self.get_u128_be(index + 128 / 8))
|
||||
}
|
||||
// fn get_address(&self, index: usize) -> CanonicalAddr {
|
||||
// 32 bytes are reserved for addresses, but only the last 20 bytes are taken by the actual address
|
||||
// CanonicalAddr::from(&self[index + 32 - 20..index + 32])
|
||||
// }
|
||||
fn get_bytes32(&self, index: usize) -> &[u8] {
|
||||
&self[index..index + 32]
|
||||
}
|
||||
|
||||
fn get_bytes(&self, index: usize, bytes: usize) -> &[u8] {
|
||||
&self[index..index + bytes]
|
||||
}
|
||||
|
||||
fn get_const_bytes<const N: usize>(&self, index: usize) -> [u8; N] {
|
||||
let mut bytes: [u8; N] = [0; N];
|
||||
bytes.copy_from_slice(&self[index..index + N]);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
/// Left-pad a 20 byte address with 0s
|
||||
//pub fn extend_address_to_32(addr: &CanonicalAddr) -> Vec<u8> {
|
||||
// extend_address_to_32_array(addr).to_vec()
|
||||
//}
|
||||
|
||||
//pub fn extend_address_to_32_array(addr: &CanonicalAddr) -> [u8; 32] {
|
||||
// let mut v: Vec<u8> = vec![0; 12];
|
||||
// v.extend(addr.as_slice());
|
||||
// let mut result: [u8; 32] = [0; 32];
|
||||
// result.copy_from_slice(&v);
|
||||
// result
|
||||
//}
|
||||
|
||||
/// Turn a string into a fixed length array. If the string is shorter than the
|
||||
/// resulting array, it gets padded with \0s on the right. If longer, it gets
|
||||
/// truncated.
|
||||
pub fn string_to_array<const N: usize>(s: &str) -> [u8; N] {
|
||||
let bytes = s.as_bytes();
|
||||
let len = usize::min(N, bytes.len());
|
||||
let zeros = vec![0; N - len];
|
||||
let padded = [bytes[..len].to_vec(), zeros].concat();
|
||||
let mut result: [u8; N] = [0; N];
|
||||
result.copy_from_slice(&padded);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn extend_string_to_32(s: &str) -> Vec<u8> {
|
||||
string_to_array::<32>(s).to_vec()
|
||||
}
|
||||
|
||||
pub fn get_string_from_32(v: &[u8]) -> String {
|
||||
let s = String::from_utf8_lossy(v);
|
||||
s.chars().filter(|c| c != &'\0').collect()
|
||||
}
|
|
@ -0,0 +1,612 @@
|
|||
use {
|
||||
near_sdk::{
|
||||
borsh::{
|
||||
self,
|
||||
BorshDeserialize,
|
||||
BorshSerialize,
|
||||
},
|
||||
collections::{
|
||||
LookupMap,
|
||||
UnorderedSet,
|
||||
},
|
||||
env,
|
||||
near_bindgen,
|
||||
require,
|
||||
AccountId,
|
||||
Balance,
|
||||
Gas,
|
||||
Promise,
|
||||
PromiseOrValue,
|
||||
PublicKey,
|
||||
},
|
||||
serde::Serialize,
|
||||
};
|
||||
|
||||
pub mod byte_utils;
|
||||
|
||||
pub mod state;
|
||||
|
||||
use crate::byte_utils::{
|
||||
get_string_from_32,
|
||||
ByteUtils,
|
||||
};
|
||||
|
||||
const CHAIN_ID_NEAR: u16 = 15;
|
||||
const CHAIN_ID_SOL: u16 = 1;
|
||||
|
||||
#[derive(BorshDeserialize, BorshSerialize)]
|
||||
pub struct GuardianAddress {
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(BorshDeserialize, BorshSerialize)]
|
||||
pub struct GuardianSetInfo {
|
||||
pub addresses: Vec<GuardianAddress>,
|
||||
pub expiration_time: u64, // Guardian set expiration time
|
||||
}
|
||||
|
||||
impl GuardianSetInfo {
|
||||
pub fn quorum(&self) -> usize {
|
||||
((self.addresses.len() * 10 / 3) * 2) / 10 + 1
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct WormholeEvent {
|
||||
standard: String,
|
||||
event: String,
|
||||
data: String,
|
||||
nonce: u32,
|
||||
emitter: String,
|
||||
seq: u64,
|
||||
block: u64,
|
||||
}
|
||||
|
||||
impl WormholeEvent {
|
||||
fn to_json_string(&self) -> String {
|
||||
// Events cannot fail to serialize so fine to panic on error
|
||||
#[allow(clippy::redundant_closure)]
|
||||
serde_json::to_string(self)
|
||||
.ok()
|
||||
.unwrap_or_else(|| env::abort())
|
||||
}
|
||||
|
||||
fn to_json_event_string(&self) -> String {
|
||||
format!("EVENT_JSON:{}", self.to_json_string())
|
||||
}
|
||||
|
||||
/// Logs the event to the host. This is required to ensure that the event is triggered
|
||||
/// and to consume the event.
|
||||
pub(crate) fn emit(self) {
|
||||
near_sdk::env::log_str(&self.to_json_event_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
#[derive(BorshDeserialize, BorshSerialize)]
|
||||
pub struct OldWormhole {
|
||||
guardians: LookupMap<u32, GuardianSetInfo>,
|
||||
dups: UnorderedSet<Vec<u8>>,
|
||||
emitters: LookupMap<String, u64>,
|
||||
guardian_set_expirity: u64,
|
||||
guardian_set_index: u32,
|
||||
owner_pk: PublicKey,
|
||||
upgrade_hash: Vec<u8>,
|
||||
message_fee: u128,
|
||||
bank: u128,
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
#[derive(BorshDeserialize, BorshSerialize)]
|
||||
pub struct Wormhole {
|
||||
guardians: LookupMap<u32, GuardianSetInfo>,
|
||||
dups: UnorderedSet<Vec<u8>>,
|
||||
emitters: LookupMap<String, u64>,
|
||||
guardian_set_expirity: u64,
|
||||
guardian_set_index: u32,
|
||||
owner_pk: PublicKey,
|
||||
upgrade_hash: Vec<u8>,
|
||||
message_fee: u128,
|
||||
bank: u128,
|
||||
}
|
||||
|
||||
impl Default for Wormhole {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
guardians: LookupMap::new(b"gs".to_vec()),
|
||||
dups: UnorderedSet::new(b"d".to_vec()),
|
||||
emitters: LookupMap::new(b"e".to_vec()),
|
||||
guardian_set_index: u32::MAX,
|
||||
guardian_set_expirity: 24 * 60 * 60 * 1_000_000_000, // 24 hours in nanoseconds
|
||||
owner_pk: env::signer_account_pk(),
|
||||
upgrade_hash: b"".to_vec(),
|
||||
message_fee: 0,
|
||||
bank: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing is mutable...
|
||||
fn parse_and_verify_vaa(storage: &Wormhole, data: &[u8]) -> state::ParsedVAA {
|
||||
let vaa = state::ParsedVAA::parse(data);
|
||||
if vaa.version != 1 {
|
||||
env::panic_str("InvalidVersion");
|
||||
}
|
||||
let guardian_set = storage
|
||||
.guardians
|
||||
.get(&vaa.guardian_set_index)
|
||||
.expect("InvalidGuardianSetIndex");
|
||||
|
||||
if guardian_set.expiration_time != 0 && guardian_set.expiration_time < env::block_timestamp() {
|
||||
env::panic_str("GuardianSetExpired");
|
||||
}
|
||||
|
||||
if (vaa.len_signers as usize) < guardian_set.quorum() {
|
||||
env::panic_str("ContractError");
|
||||
}
|
||||
|
||||
// Lets calculate the digest that we are comparing against
|
||||
let mut pos =
|
||||
state::ParsedVAA::HEADER_LEN + (vaa.len_signers * state::ParsedVAA::SIGNATURE_LEN); // SIGNATURE_LEN: usize = 66;
|
||||
let p1 = env::keccak256(&data[pos..]);
|
||||
let digest = env::keccak256(&p1);
|
||||
|
||||
// Verify guardian signatures
|
||||
let mut last_index: i32 = -1;
|
||||
pos = state::ParsedVAA::HEADER_LEN; // HEADER_LEN: usize = 6;
|
||||
|
||||
for _ in 0..vaa.len_signers {
|
||||
// which guardian signature is this?
|
||||
let index = data.get_u8(pos) as i32;
|
||||
|
||||
// We can't go backwards or use the same guardian over again
|
||||
if index <= last_index {
|
||||
env::panic_str("WrongGuardianIndexOrder");
|
||||
}
|
||||
last_index = index;
|
||||
|
||||
pos += 1; // walk forward
|
||||
|
||||
// Grab the whole signature
|
||||
let signature = &data[(pos)..(pos + state::ParsedVAA::SIG_DATA_LEN)]; // SIG_DATA_LEN: usize = 64;
|
||||
let key = guardian_set.addresses.get(index as usize).unwrap();
|
||||
|
||||
pos += state::ParsedVAA::SIG_DATA_LEN; // SIG_DATA_LEN: usize = 64;
|
||||
let recovery = data.get_u8(pos);
|
||||
|
||||
let v = env::ecrecover(&digest, signature, recovery, true).expect("cannot recover key");
|
||||
let k = &env::keccak256(&v)[12..32];
|
||||
if k != key.bytes {
|
||||
env::log_str(&format!(
|
||||
"wormhole/{}#{}: signature_error: {} != {}",
|
||||
file!(),
|
||||
line!(),
|
||||
hex::encode(&k),
|
||||
hex::encode(&key.bytes),
|
||||
));
|
||||
|
||||
env::panic_str("GuardianSignatureError");
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
|
||||
vaa
|
||||
}
|
||||
|
||||
fn vaa_update_contract(
|
||||
storage: &mut Wormhole,
|
||||
_vaa: &state::ParsedVAA,
|
||||
data: &[u8],
|
||||
deposit: Balance,
|
||||
refund_to: AccountId,
|
||||
) -> PromiseOrValue<bool> {
|
||||
let chain = data.get_u16(33);
|
||||
if chain != CHAIN_ID_NEAR {
|
||||
env::panic_str("InvalidContractUpgradeChain");
|
||||
}
|
||||
|
||||
let uh = data.get_bytes32(0);
|
||||
env::log_str(&format!(
|
||||
"wormhole/{}#{}: vaa_update_contract: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
hex::encode(&uh)
|
||||
));
|
||||
storage.upgrade_hash = uh.to_vec();
|
||||
|
||||
if deposit > 0 {
|
||||
PromiseOrValue::Promise(Promise::new(refund_to).transfer(deposit))
|
||||
} else {
|
||||
PromiseOrValue::Value(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn vaa_update_guardian_set(
|
||||
storage: &mut Wormhole,
|
||||
_vaa: &state::ParsedVAA,
|
||||
data: &[u8],
|
||||
mut deposit: Balance,
|
||||
refund_to: AccountId,
|
||||
) -> PromiseOrValue<bool> {
|
||||
const ADDRESS_LEN: usize = 20;
|
||||
let new_guardian_set_index = data.get_u32(0);
|
||||
|
||||
if storage.guardian_set_index + 1 != new_guardian_set_index {
|
||||
env::panic_str("InvalidGovernanceSetIndex");
|
||||
}
|
||||
|
||||
let n_guardians = data.get_u8(4);
|
||||
|
||||
let mut addresses = vec![];
|
||||
|
||||
for i in 0..n_guardians {
|
||||
let pos = 5 + (i as usize) * ADDRESS_LEN;
|
||||
addresses.push(GuardianAddress {
|
||||
bytes: data[pos..pos + ADDRESS_LEN].to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
let guardian_set = &mut storage
|
||||
.guardians
|
||||
.get(&storage.guardian_set_index)
|
||||
.expect("InvalidPreviousGuardianSetIndex");
|
||||
|
||||
guardian_set.expiration_time = env::block_timestamp() + storage.guardian_set_expirity;
|
||||
|
||||
storage
|
||||
.guardians
|
||||
.insert(&storage.guardian_set_index, guardian_set);
|
||||
|
||||
let g = GuardianSetInfo {
|
||||
addresses,
|
||||
expiration_time: 0,
|
||||
};
|
||||
|
||||
let storage_used = env::storage_usage();
|
||||
|
||||
storage.guardians.insert(&new_guardian_set_index, &g);
|
||||
storage.guardian_set_index = new_guardian_set_index;
|
||||
|
||||
let required_cost =
|
||||
(Balance::from(env::storage_usage() - storage_used)) * env::storage_byte_cost();
|
||||
|
||||
if required_cost > deposit {
|
||||
env::panic_str("DepositUnderflowForGuardianSet");
|
||||
}
|
||||
deposit -= required_cost;
|
||||
|
||||
if deposit > 0 {
|
||||
PromiseOrValue::Promise(Promise::new(refund_to).transfer(deposit))
|
||||
} else {
|
||||
PromiseOrValue::Value(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_set_fee(
|
||||
storage: &mut Wormhole,
|
||||
_vaa: &state::ParsedVAA,
|
||||
payload: &[u8],
|
||||
deposit: Balance,
|
||||
refund_to: AccountId,
|
||||
) -> PromiseOrValue<bool> {
|
||||
let (_, amount) = payload.get_u256(0);
|
||||
|
||||
storage.message_fee = amount as u128;
|
||||
|
||||
if deposit > 0 {
|
||||
PromiseOrValue::Promise(Promise::new(refund_to).transfer(deposit))
|
||||
} else {
|
||||
PromiseOrValue::Value(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_transfer_fee(
|
||||
storage: &mut Wormhole,
|
||||
_vaa: &state::ParsedVAA,
|
||||
payload: &[u8],
|
||||
deposit: Balance,
|
||||
) -> PromiseOrValue<bool> {
|
||||
let (_, amount) = payload.get_u256(0);
|
||||
let destination = payload.get_bytes32(32).to_vec();
|
||||
|
||||
if amount > storage.bank {
|
||||
env::panic_str("bankUnderFlow");
|
||||
}
|
||||
|
||||
// We only support addresses 32 bytes or shorter... No, we don't
|
||||
// support hash addresses in this governance message
|
||||
let d = AccountId::new_unchecked(get_string_from_32(&destination));
|
||||
|
||||
if (deposit + amount) > 0 {
|
||||
storage.bank -= amount;
|
||||
PromiseOrValue::Promise(Promise::new(d).transfer(deposit + amount))
|
||||
} else {
|
||||
PromiseOrValue::Value(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[near_bindgen]
|
||||
impl Wormhole {
|
||||
// I like passing the vaa's as strings around since it will show
|
||||
// up better in explorers... I'll let a near sensai talk me out
|
||||
// of this...
|
||||
pub fn verify_vaa(&self, vaa: String) -> u32 {
|
||||
let g1 = env::used_gas();
|
||||
let h = hex::decode(vaa).expect("invalidVaa");
|
||||
parse_and_verify_vaa(self, &h);
|
||||
let g2 = env::used_gas();
|
||||
|
||||
env::log_str(&format!(
|
||||
"wormhole/{}#{}: vaa_verify: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
serde_json::to_string(&(g2 - g1)).unwrap()
|
||||
));
|
||||
|
||||
self.guardian_set_index as u32
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn register_emitter(&mut self, emitter: String) -> PromiseOrValue<bool> {
|
||||
let storage_used = env::storage_usage();
|
||||
|
||||
self.emitters.insert(&emitter, &1);
|
||||
|
||||
if env::storage_usage() < storage_used {
|
||||
env::panic_str("ImpossibleStorage");
|
||||
}
|
||||
|
||||
let required_cost =
|
||||
(Balance::from(env::storage_usage() - storage_used)) * env::storage_byte_cost();
|
||||
let mut deposit = env::attached_deposit();
|
||||
if required_cost > deposit {
|
||||
env::panic_str("DepositUnderflowForToken2");
|
||||
}
|
||||
|
||||
deposit -= required_cost;
|
||||
|
||||
if deposit > 0 {
|
||||
PromiseOrValue::Promise(Promise::new(env::predecessor_account_id()).transfer(deposit))
|
||||
} else {
|
||||
PromiseOrValue::Value(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn publish_message(&mut self, data: String, nonce: u32) -> u64 {
|
||||
require!(
|
||||
env::prepaid_gas() >= Gas(10_000_000_000_000),
|
||||
&format!(
|
||||
"wormhole/{}#{}: more gas is required {}",
|
||||
file!(),
|
||||
line!(),
|
||||
serde_json::to_string(&env::prepaid_gas()).unwrap()
|
||||
)
|
||||
);
|
||||
|
||||
require!(
|
||||
env::attached_deposit() >= self.message_fee,
|
||||
"message_fee not provided"
|
||||
);
|
||||
self.bank += env::attached_deposit();
|
||||
|
||||
let s = env::predecessor_account_id().to_string();
|
||||
|
||||
if !self.emitters.contains_key(&s) {
|
||||
env::panic_str("EmitterNotRegistered");
|
||||
}
|
||||
|
||||
let seq = self.emitters.get(&s).unwrap();
|
||||
|
||||
self.emitters.insert(&s, &(seq + 1));
|
||||
|
||||
WormholeEvent {
|
||||
standard: "wormhole".to_string(),
|
||||
event: "publish".to_string(),
|
||||
data,
|
||||
nonce,
|
||||
emitter: hex::encode(env::sha256(s.as_bytes())),
|
||||
seq,
|
||||
block: env::block_height(),
|
||||
}
|
||||
.emit();
|
||||
seq
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn submit_vaa(&mut self, vaa: String) -> PromiseOrValue<bool> {
|
||||
let refund_to = env::predecessor_account_id();
|
||||
let mut deposit = env::attached_deposit();
|
||||
|
||||
if env::attached_deposit() == 0 {
|
||||
env::panic_str("PayForStorage");
|
||||
}
|
||||
|
||||
if env::prepaid_gas() < Gas(150_000_000_000_000) {
|
||||
env::panic_str("NotEnoughGas");
|
||||
}
|
||||
|
||||
let h = hex::decode(vaa).expect("invalidVaa");
|
||||
let vaa = parse_and_verify_vaa(self, &h);
|
||||
|
||||
// Check if VAA with this hash was already accepted
|
||||
if self.dups.contains(&vaa.hash) {
|
||||
env::panic_str("alreadyExecuted");
|
||||
}
|
||||
|
||||
let storage_used = env::storage_usage();
|
||||
self.dups.insert(&vaa.hash);
|
||||
let required_cost =
|
||||
(Balance::from(env::storage_usage() - storage_used)) * env::storage_byte_cost();
|
||||
|
||||
if required_cost > deposit {
|
||||
env::panic_str("DepositUnderflowForDupSuppression");
|
||||
}
|
||||
deposit -= required_cost;
|
||||
|
||||
if (CHAIN_ID_SOL != vaa.emitter_chain)
|
||||
|| (hex::decode("0000000000000000000000000000000000000000000000000000000000000004")
|
||||
.unwrap()
|
||||
!= vaa.emitter_address)
|
||||
{
|
||||
env::panic_str("InvalidGovernanceEmitter");
|
||||
}
|
||||
|
||||
// This is the core contract... it SHOULD only get governance packets and be on the latest
|
||||
|
||||
if self.guardian_set_index != vaa.guardian_set_index {
|
||||
env::panic_str("InvalidGovernanceSet");
|
||||
}
|
||||
|
||||
let data: &[u8] = &vaa.payload;
|
||||
|
||||
if data[0..32]
|
||||
!= hex::decode("00000000000000000000000000000000000000000000000000000000436f7265")
|
||||
.unwrap()
|
||||
{
|
||||
env::panic_str("InvalidGovernanceModule");
|
||||
}
|
||||
|
||||
let chain = data.get_u16(33);
|
||||
let action = data.get_u8(32);
|
||||
|
||||
if !((action == 2 && chain == 0) || chain == CHAIN_ID_NEAR) {
|
||||
env::panic_str("InvalidGovernanceChain");
|
||||
}
|
||||
|
||||
let payload = &data[35..];
|
||||
|
||||
match action {
|
||||
1u8 => vaa_update_contract(self, &vaa, payload, deposit, refund_to),
|
||||
2u8 => vaa_update_guardian_set(self, &vaa, payload, deposit, refund_to),
|
||||
3u8 => handle_set_fee(self, &vaa, payload, deposit, refund_to),
|
||||
4u8 => handle_transfer_fee(self, &vaa, payload, deposit),
|
||||
_ => env::panic_str("InvalidGovernanceAction"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message_fee(&self) -> u128 {
|
||||
self.message_fee
|
||||
}
|
||||
|
||||
pub fn boot_wormhole(&mut self, gset: u32, addresses: Vec<String>) {
|
||||
if self.owner_pk != env::signer_account_pk() {
|
||||
env::panic_str("invalidSigner");
|
||||
}
|
||||
|
||||
assert!(self.guardian_set_index == u32::MAX);
|
||||
|
||||
let addr = addresses
|
||||
.iter()
|
||||
.map(|address| GuardianAddress {
|
||||
bytes: hex::decode(address).unwrap(),
|
||||
})
|
||||
.collect::<Vec<GuardianAddress>>();
|
||||
|
||||
let g = GuardianSetInfo {
|
||||
addresses: addr,
|
||||
expiration_time: 0,
|
||||
};
|
||||
self.guardians.insert(&gset, &g);
|
||||
self.guardian_set_index = gset;
|
||||
env::log_str(&format!("Booting guardian_set_index {}", gset));
|
||||
}
|
||||
|
||||
#[private]
|
||||
pub fn update_contract_done(
|
||||
&mut self,
|
||||
refund_to: near_sdk::AccountId,
|
||||
storage_used: u64,
|
||||
attached_deposit: u128,
|
||||
) {
|
||||
let delta = (env::storage_usage() as i128 - storage_used as i128)
|
||||
* env::storage_byte_cost() as i128;
|
||||
let refund = attached_deposit as i128 - delta;
|
||||
if refund > 0 {
|
||||
env::log_str(&format!(
|
||||
"wormhole/{}#{}: update_contract_done: refund {} to {}",
|
||||
file!(),
|
||||
line!(),
|
||||
refund,
|
||||
refund_to
|
||||
));
|
||||
Promise::new(refund_to).transfer(refund as u128);
|
||||
}
|
||||
}
|
||||
|
||||
#[private]
|
||||
fn update_contract_work(&mut self, v: Vec<u8>) -> Promise {
|
||||
if env::attached_deposit() == 0 {
|
||||
env::panic_str("attach some cash");
|
||||
}
|
||||
|
||||
let s = env::sha256(&v);
|
||||
|
||||
env::log_str(&format!(
|
||||
"wormhole/{}#{}: update_contract: {}",
|
||||
file!(),
|
||||
line!(),
|
||||
hex::encode(&s)
|
||||
));
|
||||
|
||||
if s.to_vec() != self.upgrade_hash {
|
||||
env::panic_str("invalidUpgradeContract");
|
||||
}
|
||||
|
||||
Promise::new(env::current_account_id())
|
||||
.deploy_contract(v.to_vec())
|
||||
.then(Self::ext(env::current_account_id()).update_contract_done(
|
||||
env::predecessor_account_id(),
|
||||
env::storage_usage(),
|
||||
env::attached_deposit(),
|
||||
))
|
||||
}
|
||||
|
||||
#[payable]
|
||||
pub fn pass(&mut self) -> bool {
|
||||
env::log_str(&format!("wormhole::pass {} {}", file!(), line!()));
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[init(ignore_state)]
|
||||
#[payable]
|
||||
pub fn migrate() -> Self {
|
||||
// call migrate on self
|
||||
if env::attached_deposit() != 1 {
|
||||
env::panic_str("Need money");
|
||||
}
|
||||
let old_state: OldWormhole = env::state_read().expect("failed");
|
||||
if env::signer_account_pk() != old_state.owner_pk {
|
||||
env::panic_str("CannotCallMigrate");
|
||||
}
|
||||
env::log_str(&format!("wormhole/{}#{}: migrate", file!(), line!(),));
|
||||
Self {
|
||||
guardians: old_state.guardians,
|
||||
dups: old_state.dups,
|
||||
emitters: old_state.emitters,
|
||||
guardian_set_expirity: old_state.guardian_set_expirity,
|
||||
guardian_set_index: old_state.guardian_set_index,
|
||||
owner_pk: old_state.owner_pk,
|
||||
upgrade_hash: old_state.upgrade_hash,
|
||||
message_fee: old_state.message_fee,
|
||||
bank: old_state.bank,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let result = await userAccount.functionCall({
|
||||
// contractId: config.wormholeAccount,
|
||||
// methodName: "update_contract",
|
||||
// args: await fs.readFileSync("...."),
|
||||
// attachedDeposit: "12500000000000000000000",
|
||||
// gas: 300000000000000,
|
||||
// });
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn update_contract() {
|
||||
env::setup_panic_hook();
|
||||
let mut contract: Wormhole = env::state_read().expect("Contract is not initialized");
|
||||
contract.update_contract_work(env::input().unwrap());
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
use {
|
||||
crate::byte_utils::ByteUtils,
|
||||
near_sdk::env,
|
||||
};
|
||||
|
||||
// Validator Action Approval(VAA) data
|
||||
|
||||
pub struct ParsedVAA {
|
||||
pub version: u8,
|
||||
pub guardian_set_index: u32,
|
||||
pub timestamp: u32,
|
||||
pub nonce: u32,
|
||||
pub len_signers: usize,
|
||||
|
||||
pub emitter_chain: u16,
|
||||
pub emitter_address: Vec<u8>,
|
||||
pub sequence: u64,
|
||||
pub consistency_level: u8,
|
||||
pub payload: Vec<u8>,
|
||||
|
||||
pub hash: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ParsedVAA {
|
||||
/* VAA format:
|
||||
|
||||
header (length 6):
|
||||
0 uint8 version (0x01)
|
||||
1 uint32 guardian set index
|
||||
5 uint8 len signatures
|
||||
|
||||
per signature (length 66):
|
||||
0 uint8 index of the signer (in guardian keys)
|
||||
1 [65]uint8 signature
|
||||
|
||||
body:
|
||||
0 uint32 timestamp (unix in seconds)
|
||||
4 uint32 nonce
|
||||
8 uint16 emitter_chain
|
||||
10 [32]uint8 emitter_address
|
||||
42 uint64 sequence
|
||||
50 uint8 consistency_level
|
||||
51 []uint8 payload
|
||||
*/
|
||||
|
||||
pub const HEADER_LEN: usize = 6;
|
||||
pub const SIGNATURE_LEN: usize = 66;
|
||||
|
||||
pub const GUARDIAN_SET_INDEX_POS: usize = 1;
|
||||
pub const LEN_SIGNER_POS: usize = 5;
|
||||
|
||||
pub const VAA_NONCE_POS: usize = 4;
|
||||
pub const VAA_EMITTER_CHAIN_POS: usize = 8;
|
||||
pub const VAA_EMITTER_ADDRESS_POS: usize = 10;
|
||||
pub const VAA_SEQUENCE_POS: usize = 42;
|
||||
pub const VAA_CONSISTENCY_LEVEL_POS: usize = 50;
|
||||
pub const VAA_PAYLOAD_POS: usize = 51;
|
||||
|
||||
// Signature data offsets in the signature block
|
||||
pub const SIG_DATA_POS: usize = 1;
|
||||
// Signature length minus recovery id at the end
|
||||
pub const SIG_DATA_LEN: usize = 64;
|
||||
// Recovery byte is last after the main signature
|
||||
pub const SIG_RECOVERY_POS: usize = Self::SIG_DATA_POS + Self::SIG_DATA_LEN;
|
||||
|
||||
pub fn parse(data: &[u8]) -> Self {
|
||||
let version = data.get_u8(0);
|
||||
|
||||
// Load 4 bytes starting from index 1
|
||||
let guardian_set_index: u32 = data.get_u32(Self::GUARDIAN_SET_INDEX_POS);
|
||||
let len_signers = data.get_u8(Self::LEN_SIGNER_POS) as usize;
|
||||
let body_offset: usize = Self::HEADER_LEN + Self::SIGNATURE_LEN * len_signers as usize;
|
||||
|
||||
// Hash the body
|
||||
if body_offset >= data.len() {
|
||||
env::panic_str("InvalidVAA");
|
||||
}
|
||||
let body = &data[body_offset..];
|
||||
|
||||
let hash = env::keccak256(body);
|
||||
|
||||
// Signatures valid, apply VAA
|
||||
if body_offset + Self::VAA_PAYLOAD_POS > data.len() {
|
||||
env::panic_str("InvalidVAA");
|
||||
}
|
||||
|
||||
let timestamp = data.get_u32(body_offset);
|
||||
let nonce = data.get_u32(body_offset + Self::VAA_NONCE_POS);
|
||||
let emitter_chain = data.get_u16(body_offset + Self::VAA_EMITTER_CHAIN_POS);
|
||||
let emitter_address = data
|
||||
.get_bytes32(body_offset + Self::VAA_EMITTER_ADDRESS_POS)
|
||||
.to_vec();
|
||||
let sequence = data.get_u64(body_offset + Self::VAA_SEQUENCE_POS);
|
||||
let consistency_level = data.get_u8(body_offset + Self::VAA_CONSISTENCY_LEVEL_POS);
|
||||
let payload = data[body_offset + Self::VAA_PAYLOAD_POS..].to_vec();
|
||||
|
||||
ParsedVAA {
|
||||
version,
|
||||
guardian_set_index,
|
||||
timestamp,
|
||||
nonce,
|
||||
len_signers: len_signers as usize,
|
||||
emitter_chain,
|
||||
emitter_address,
|
||||
sequence,
|
||||
consistency_level,
|
||||
payload,
|
||||
hash,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash -f
|
||||
|
||||
npx ts-node devnet_deploy.ts
|
|
@ -0,0 +1,346 @@
|
|||
// npx pretty-quick
|
||||
|
||||
const nearAPI = require("near-api-js");
|
||||
const BN = require("bn.js");
|
||||
const fs = require("fs");
|
||||
const fetch = require("node-fetch");
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
|
||||
function getConfig(env: any) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
wormholeAccount: "wormhole.test.near",
|
||||
tokenAccount: "token.test.near",
|
||||
nftAccount: "nft.test.near",
|
||||
testAccount: "test.test.near",
|
||||
};
|
||||
case "testnet":
|
||||
return {
|
||||
networkId: "testnet",
|
||||
nodeUrl: "https://rpc.testnet.near.org",
|
||||
masterAccount: "wormhole.testnet",
|
||||
wormholeAccount: "wormhole.wormhole.testnet",
|
||||
tokenAccount: "token.wormhole.testnet",
|
||||
nftAccount: "nft.wormhole.testnet",
|
||||
testAccount: "test.wormhole.testnet",
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async function initNear() {
|
||||
let e = process.env.NEAR_ENV || "sandbox";
|
||||
|
||||
let config = getConfig(e);
|
||||
|
||||
let masterKey: any;
|
||||
|
||||
if (e === "sandbox") {
|
||||
// Retrieve the validator key directly in the Tilt environment
|
||||
const response = await fetch("http://localhost:3031/validator_key.json");
|
||||
|
||||
const keyFile = await response.json();
|
||||
|
||||
masterKey = nearAPI.utils.KeyPair.fromString(
|
||||
keyFile.secret_key || keyFile.private_key
|
||||
);
|
||||
} else {
|
||||
masterKey = nearAPI.utils.KeyPair.fromString(process.env.NEAR_PK);
|
||||
}
|
||||
let masterPubKey = masterKey.getPublicKey();
|
||||
|
||||
let keyStore = new nearAPI.keyStores.InMemoryKeyStore();
|
||||
keyStore.setKey(config.networkId, config.masterAccount, masterKey);
|
||||
|
||||
let near = await nearAPI.connect({
|
||||
deps: {
|
||||
keyStore,
|
||||
},
|
||||
networkId: config.networkId,
|
||||
nodeUrl: config.nodeUrl,
|
||||
});
|
||||
let masterAccount = new nearAPI.Account(
|
||||
near.connection,
|
||||
config.masterAccount
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR masterAccount: " +
|
||||
JSON.stringify(await masterAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
const wormholeContract = await fs.readFileSync("./near_wormhole.wasm");
|
||||
const tokenContract = await fs.readFileSync("./near_token_bridge.wasm");
|
||||
const nftContract = await fs.readFileSync("./near_nft_bridge.wasm");
|
||||
const testContract = await fs.readFileSync("./near_mock_bridge_integration.wasm");
|
||||
|
||||
let wormholeAccount: any;
|
||||
|
||||
console.log("setting key for new wormhole contract");
|
||||
keyStore.setKey(config.networkId, config.wormholeAccount, masterKey);
|
||||
keyStore.setKey(config.networkId, config.tokenAccount, masterKey);
|
||||
keyStore.setKey(config.networkId, config.nftAccount, masterKey);
|
||||
|
||||
if (e === "sandbox") {
|
||||
console.log("Deploying core/wormhole contract: " + config.wormholeAccount);
|
||||
wormholeAccount = await masterAccount.createAndDeployContract(
|
||||
config.wormholeAccount,
|
||||
masterKey.getPublicKey(),
|
||||
wormholeContract,
|
||||
new BN("20000000000000000000000000")
|
||||
);
|
||||
|
||||
await wormholeAccount.functionCall({
|
||||
contractId: config.wormholeAccount,
|
||||
methodName: "register_emitter",
|
||||
args: {emitter: config.tokenAccount},
|
||||
attachedDeposit: new BN("30000000000000000000000"),
|
||||
gas: new BN("100000000000000"),
|
||||
})
|
||||
await wormholeAccount.functionCall({
|
||||
contractId: config.wormholeAccount,
|
||||
methodName: "register_emitter",
|
||||
args: {emitter: config.testAccount},
|
||||
attachedDeposit: new BN("30000000000000000000000"),
|
||||
gas: new BN("100000000000000"),
|
||||
})
|
||||
await wormholeAccount.functionCall({
|
||||
contractId: config.wormholeAccount,
|
||||
methodName: "register_emitter",
|
||||
args: {emitter: config.nftAccount},
|
||||
attachedDeposit: new BN("30000000000000000000000"),
|
||||
gas: new BN("100000000000000"),
|
||||
})
|
||||
} else {
|
||||
// This uses the standard API to redeploy ... we can migrate over to the vaa's later
|
||||
console.log(
|
||||
"redeploying core/wormhole contract: " + config.wormholeAccount
|
||||
);
|
||||
wormholeAccount = new nearAPI.Account(
|
||||
near.connection,
|
||||
config.wormholeAccount
|
||||
);
|
||||
await wormholeAccount.deployContract(wormholeContract);
|
||||
|
||||
// console.log("migrating " + config.wormholeAccount);
|
||||
// console.log(
|
||||
// await wormholeAccount.functionCall({
|
||||
// contractId: config.wormholeAccount,
|
||||
// methodName: "migrate",
|
||||
// args: {},
|
||||
// attachedDeposit: new BN(1),
|
||||
// gas: new BN("100000000000000"),
|
||||
// })
|
||||
// );
|
||||
// console.log("done migrating " + config.tokenAccount);
|
||||
|
||||
}
|
||||
|
||||
let tokenAccount: any;
|
||||
|
||||
if (e === "sandbox") {
|
||||
console.log("Deploying token bridgecontract: " + config.tokenAccount);
|
||||
tokenAccount = await masterAccount.createAndDeployContract(
|
||||
config.tokenAccount,
|
||||
masterKey.getPublicKey(),
|
||||
tokenContract,
|
||||
new BN("20000000000000000000000000")
|
||||
);
|
||||
} else {
|
||||
// This uses the standard API to redeploy ... we can migrate over to the vaa's later
|
||||
console.log("redeploying token bridge contract: " + config.tokenAccount);
|
||||
tokenAccount = new nearAPI.Account(near.connection, config.tokenAccount);
|
||||
await tokenAccount.deployContract(tokenContract);
|
||||
|
||||
console.log( await tokenAccount.viewFunction(config.tokenAccount, "emitter", {}));
|
||||
|
||||
// console.log("migrating " + config.tokenAccount);
|
||||
// console.log(
|
||||
// await tokenAccount.functionCall({
|
||||
// contractId: config.tokenAccount,
|
||||
// methodName: "migrate",
|
||||
// args: {},
|
||||
// attachedDeposit: new BN(1),
|
||||
// gas: new BN("100000000000000"),
|
||||
// })
|
||||
// );
|
||||
// console.log("done migrating " + config.tokenAccount);
|
||||
}
|
||||
|
||||
let nftAccount: any;
|
||||
|
||||
if (e === "sandbox") {
|
||||
console.log("Deploying nft bridge contract: " + config.nftAccount);
|
||||
let nftAccount = await masterAccount.createAndDeployContract(
|
||||
config.nftAccount,
|
||||
masterKey.getPublicKey(),
|
||||
nftContract,
|
||||
new BN("20000000000000000000000000")
|
||||
);
|
||||
} else {
|
||||
// This uses the standard API to redeploy ... we can migrate over to the vaa's later
|
||||
console.log("redeploying nft contract: " + config.nftAccount);
|
||||
nftAccount = new nearAPI.Account(near.connection, config.nftAccount);
|
||||
await nftAccount.deployContract(nftContract);
|
||||
}
|
||||
|
||||
let lines: any;
|
||||
|
||||
if (e === "sandbox") {
|
||||
console.log("Deploying mach contract to " + config.testAccount);
|
||||
let testAccount = await masterAccount.createAndDeployContract(
|
||||
config.testAccount,
|
||||
masterKey.getPublicKey(),
|
||||
testContract,
|
||||
new BN("25000000000000000000000000")
|
||||
);
|
||||
|
||||
console.log("booting wormhole to devnet keys");
|
||||
|
||||
lines = fs.readFileSync(".env", "utf-8").split("\n");
|
||||
} else {
|
||||
console.log("booting wormhole to testnet keys");
|
||||
|
||||
lines = fs.readFileSync("/home/jsiegel/testnet-env", "utf-8").split("\n");
|
||||
}
|
||||
|
||||
// console.log(lines);
|
||||
let signers: any[] = [];
|
||||
|
||||
let vaasToken: any[] = [];
|
||||
let vaasNFT: any[] = [];
|
||||
|
||||
lines.forEach((line: any) => {
|
||||
let f = line.split("=");
|
||||
if (f[0] === "INIT_SIGNERS") {
|
||||
signers = eval(f[1]);
|
||||
}
|
||||
if (f[0].startsWith("REGISTER_") && f[0].endsWith("TOKEN_BRIDGE_VAA")) {
|
||||
vaasToken.push(f[1]);
|
||||
} else if (f[0].endsWith("TOKEN_BRIDGE_VAA_REGISTER")) {
|
||||
vaasToken.push(f[1]);
|
||||
}
|
||||
|
||||
if (f[0].startsWith("REGISTER_") && f[0].endsWith("NFT_BRIDGE_VAA")) {
|
||||
vaasNFT.push(f[1]);
|
||||
} else if (
|
||||
f[0].endsWith("NFT_BRIDGE_VAA") ||
|
||||
f[0].endsWith("NFT_BRIDGE_VAA_REGISTER")
|
||||
) {
|
||||
vaasNFT.push(f[1]);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(vaasToken);
|
||||
console.log(vaasNFT);
|
||||
|
||||
if (e === "sandbox") {
|
||||
let result = await masterAccount.functionCall({
|
||||
contractId: config.wormholeAccount,
|
||||
methodName: "boot_wormhole",
|
||||
args: {
|
||||
gset: 0,
|
||||
addresses: signers,
|
||||
},
|
||||
gas: 100000000000000,
|
||||
});
|
||||
|
||||
console.log("Booting up the token bridge");
|
||||
|
||||
result = await masterAccount.functionCall({
|
||||
contractId: config.tokenAccount,
|
||||
methodName: "boot_portal",
|
||||
args: {
|
||||
core: config.wormholeAccount,
|
||||
},
|
||||
gas: 100000000000000,
|
||||
});
|
||||
|
||||
console.log("Booting up the nft bridge");
|
||||
|
||||
result = await masterAccount.functionCall({
|
||||
contractId: config.nftAccount,
|
||||
methodName: "boot_portal",
|
||||
args: {
|
||||
core: config.wormholeAccount,
|
||||
},
|
||||
gas: 100000000000000,
|
||||
});
|
||||
}
|
||||
|
||||
// for (const line of vaasNFT) {
|
||||
// console.log("Submitting to " + config.nftAccount + ": " + line);
|
||||
//
|
||||
// try {
|
||||
// await masterAccount.functionCall({
|
||||
// contractId: config.nftAccount,
|
||||
// methodName: "submit_vaa",
|
||||
// args: {
|
||||
// vaa: line,
|
||||
// },
|
||||
// attachedDeposit: new BN("30000000000000000000000"),
|
||||
// gas: new BN("300000000000000"),
|
||||
// });
|
||||
//
|
||||
// await masterAccount.functionCall({
|
||||
// contractId: config.nftAccount,
|
||||
// methodName: "submit_vaa",
|
||||
// args: {
|
||||
// vaa: line,
|
||||
// },
|
||||
// attachedDeposit: new BN("30000000000000000000000"),
|
||||
// gas: new BN("300000000000000"),
|
||||
// });
|
||||
// } catch {
|
||||
// console.log("Exception thrown.. ");
|
||||
// }
|
||||
// }
|
||||
|
||||
console.log("nft bridge booted");
|
||||
|
||||
for (const line of vaasToken) {
|
||||
console.log("Submitting to " + config.tokenAccount + ": " + line);
|
||||
|
||||
try {
|
||||
await masterAccount.functionCall({
|
||||
contractId: config.tokenAccount,
|
||||
methodName: "submit_vaa",
|
||||
args: {
|
||||
vaa: line,
|
||||
},
|
||||
attachedDeposit: new BN("30000000000000000000001"),
|
||||
gas: new BN("300000000000000"),
|
||||
});
|
||||
|
||||
await masterAccount.functionCall({
|
||||
contractId: config.tokenAccount,
|
||||
methodName: "submit_vaa",
|
||||
args: {
|
||||
vaa: line,
|
||||
},
|
||||
attachedDeposit: new BN("30000000000000000000001"),
|
||||
gas: new BN("300000000000000"),
|
||||
});
|
||||
} catch {
|
||||
console.log("Exception thrown.. ");
|
||||
}
|
||||
}
|
||||
|
||||
console.log("token bridge booted");
|
||||
|
||||
// console.log("deleting the master key from the token contract");
|
||||
// await tokenAccount.deleteKey(masterKey.getPublicKey());
|
||||
|
||||
// console.log("deleting the master key from the nft contract");
|
||||
// await nftAccount.deleteKey(masterKey.getPublicKey());
|
||||
|
||||
// console.log("deleting the master key from the wormhole contract");
|
||||
// await wormholeAccount.deleteKey(masterKey.getPublicKey());
|
||||
}
|
||||
|
||||
initNear();
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
apt-get update
|
||||
apt-get -y install libclang-dev jq
|
||||
|
||||
git clone https://github.com/near/nearcore
|
||||
cd nearcore
|
||||
make sandbox-release
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"private": true,
|
||||
"name": "wormhole-near",
|
||||
"version": "1.0.0",
|
||||
"description": "Wormhole near support code",
|
||||
"keywords": [
|
||||
"near-protocol",
|
||||
"wormhole",
|
||||
"rust",
|
||||
"smart-contract"
|
||||
],
|
||||
"author": "Josh Siegel",
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "file:../sdk/js",
|
||||
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
|
||||
"@openzeppelin/contracts": "^4.6.0",
|
||||
"big.js": "^5.2.2",
|
||||
"bn.js": "^5.2.0",
|
||||
"borsh": "^0.4.0",
|
||||
"bs58": "^5.0.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"ethers": "^5.6.5",
|
||||
"near-api-js": "^0.43.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"ts-node": "^10.8.1",
|
||||
"web3": "^1.7.4",
|
||||
"web3-eth-abi": "^1.7.3",
|
||||
"web3-utils": "^1.7.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"cssnano": "^5.0.7",
|
||||
"gh-pages": "^3.1.0",
|
||||
"parcel-bundler": "^1.12.5",
|
||||
"postcss": "^8.3.6",
|
||||
"sass": "^1.37.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@babel/preset-env": "7.13.8"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
targets = [ "wasm32-unknown-unknown" ]
|
||||
profile = "default"
|
|
@ -0,0 +1,17 @@
|
|||
# Merge all imports into a clean vertical list of module imports.
|
||||
imports_granularity = "One"
|
||||
group_imports = "One"
|
||||
imports_layout = "Vertical"
|
||||
|
||||
# Better grep-ability.
|
||||
empty_item_single_line = false
|
||||
|
||||
# Consistent pipe layout.
|
||||
match_arm_leading_pipes = "Preserve"
|
||||
|
||||
# Align Fields
|
||||
enum_discrim_align_threshold = 80
|
||||
struct_field_align_threshold = 80
|
||||
|
||||
# Allow up to two blank lines for visual grouping.
|
||||
blank_lines_upper_bound = 2
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source $HOME/.cargo/env
|
||||
rustup default 1.60.0
|
||||
rustup update
|
||||
rustup target add wasm32-unknown-unknown
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
|
||||
echo "starting web server"
|
||||
|
||||
python3 -m http.server --directory /tmp/_sandbox 3031 &
|
||||
|
||||
echo "starting near sandbox"
|
||||
rm -rf /tmp/_sandbox
|
||||
nearcore/target/release/near-sandbox --home /tmp/_sandbox init
|
||||
tmpfile="$(mktemp)"
|
||||
jq '.store.max_open_files = 9000' /tmp/_sandbox/config.json > "$tmpfile"
|
||||
cp "$tmpfile" /tmp/_sandbox/config.json
|
||||
nearcore/target/release/near-sandbox --home /tmp/_sandbox run
|
|
@ -0,0 +1,435 @@
|
|||
import algosdk, {
|
||||
Account,
|
||||
Algodv2,
|
||||
assignGroupID,
|
||||
makePaymentTxnWithSuggestedParamsFromObject,
|
||||
Transaction,
|
||||
waitForConfirmation,
|
||||
} from "@certusone/wormhole-sdk/node_modules/algosdk";
|
||||
|
||||
import { getForeignAssetAlgorand } from "@certusone/wormhole-sdk/lib/cjs/token_bridge";
|
||||
import { ChainId } from "@certusone/wormhole-sdk/lib/cjs/utils";
|
||||
import {
|
||||
TransactionSignerPair,
|
||||
_parseVAAAlgorand,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/algorand";
|
||||
|
||||
const ci = !!process.env.CI;
|
||||
|
||||
const ALGO_TOKEN =
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
const ALGOD_ADDRESS: string = ci ? "http://algorand" : "http://localhost";
|
||||
const ALGOD_PORT: number = 4001;
|
||||
|
||||
/**
|
||||
* Creates a new Algodv2 client using local file consts
|
||||
* @returns a newly constructed Algodv2 client
|
||||
*/
|
||||
export function getAlgoClient(): Algodv2 {
|
||||
const algodClient = new Algodv2(ALGO_TOKEN, ALGOD_ADDRESS, ALGOD_PORT);
|
||||
return algodClient;
|
||||
}
|
||||
|
||||
let KMD_TOKEN =
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
const KMD_ADDRESS: string = ci ? "http://algorand" : "http://localhost";
|
||||
const KMD_PORT: number = 4002;
|
||||
const KMD_WALLET_NAME: string = "unencrypted-default-wallet";
|
||||
const KMD_WALLET_PASSWORD: string = "";
|
||||
|
||||
export function getKmdClient(): algosdk.Kmd {
|
||||
const kmdClient: algosdk.Kmd = new algosdk.Kmd(
|
||||
KMD_TOKEN,
|
||||
KMD_ADDRESS,
|
||||
KMD_PORT
|
||||
);
|
||||
return kmdClient;
|
||||
}
|
||||
|
||||
export async function getGenesisAccounts(): Promise<Account[]> {
|
||||
let retval: Account[] = [];
|
||||
const kmd: algosdk.Kmd = getKmdClient();
|
||||
|
||||
// Get list of wallets
|
||||
const wallets = (await kmd.listWallets()).wallets;
|
||||
if (!wallets) {
|
||||
console.error("No wallets found!");
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Walk walles to find correct wallet
|
||||
let myWalletId: string = "";
|
||||
wallets.forEach((element: any) => {
|
||||
// console.log(element);
|
||||
if (element.name === KMD_WALLET_NAME) {
|
||||
myWalletId = element.id;
|
||||
}
|
||||
});
|
||||
if (myWalletId.length === 0) {
|
||||
console.error("invalid wallet ID");
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Get the wallet handle for Genesis wallet
|
||||
const myWalletHandle = (
|
||||
await kmd.initWalletHandle(myWalletId, KMD_WALLET_PASSWORD)
|
||||
).wallet_handle_token;
|
||||
// console.log("myWalletHandle:", myWalletHandle);
|
||||
|
||||
// Get the 3 addresses associated with the Genesis wallet
|
||||
const addresses = (await kmd.listKeys(myWalletHandle)).addresses;
|
||||
// console.log("addresses:", addresses);
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const element = addresses[i];
|
||||
const myExportedKey: Buffer = (
|
||||
await kmd.exportKey(myWalletHandle, KMD_WALLET_PASSWORD, element)
|
||||
).private_key;
|
||||
// console.log("exported key:", element, myExportedKey);
|
||||
let mn = algosdk.secretKeyToMnemonic(myExportedKey);
|
||||
let ta = algosdk.mnemonicToSecretKey(mn);
|
||||
|
||||
retval.push(ta);
|
||||
}
|
||||
kmd.releaseWalletHandle(myWalletHandle);
|
||||
// console.log("length of genesis accounts:", retval.length);
|
||||
return retval;
|
||||
}
|
||||
|
||||
export async function firstKmdTransaction() {
|
||||
try {
|
||||
const genAccounts = await getGenesisAccounts();
|
||||
|
||||
// const walletRsp = await myKmdClient.getWallet(myWalletHandle);
|
||||
// console.log("walletRsp:", walletRsp);
|
||||
} catch (e) {
|
||||
console.log("KMD transaction error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// This function creates temporary accounts and funds them with the
|
||||
// Genesis accounts.
|
||||
export async function getTempAccounts(): Promise<Account[]> {
|
||||
let retval: Account[] = [];
|
||||
const algodClient = getAlgoClient();
|
||||
const genesisAccounts: Account[] = await getGenesisAccounts();
|
||||
const numAccts = genesisAccounts.length;
|
||||
if (numAccts === 0) {
|
||||
console.error("Failed to get genesisAccounts");
|
||||
return retval;
|
||||
}
|
||||
// console.log("About to construct txns...");
|
||||
const params = await algodClient.getTransactionParams().do();
|
||||
let transactions: Transaction[] = [];
|
||||
for (let i = 0; i < numAccts; i++) {
|
||||
let newAcct = createAccount();
|
||||
if (!newAcct) {
|
||||
throw new Error("failed to create a temp account");
|
||||
}
|
||||
let fundingAcct = genesisAccounts[i];
|
||||
// Create a payment transaction
|
||||
// console.log(
|
||||
// "Creating paytxn with fundAcct",
|
||||
// fundingAcct,
|
||||
// "newAcct",
|
||||
// newAcct
|
||||
// );
|
||||
const payTxn = makePaymentTxnWithSuggestedParamsFromObject({
|
||||
from: fundingAcct.addr,
|
||||
to: newAcct.addr,
|
||||
amount: 15000000,
|
||||
suggestedParams: params,
|
||||
});
|
||||
// Sign the transaction
|
||||
// console.log("signing paytxn...");
|
||||
const signedTxn = payTxn.signTxn(fundingAcct.sk);
|
||||
const signedTxnId = payTxn.txID().toString();
|
||||
// console.log("signedTxnId:", signedTxnId);
|
||||
// Submit the transaction
|
||||
// console.log("submitting transaction...");
|
||||
const txId = await algodClient.sendRawTransaction(signedTxn).do();
|
||||
// console.log("submitted txId:", txId);
|
||||
// Wait for response
|
||||
const confirmedTxn = await algosdk.waitForConfirmation(
|
||||
algodClient,
|
||||
signedTxnId,
|
||||
4
|
||||
);
|
||||
//Get the completed Transaction
|
||||
// console.log(
|
||||
// "Transaction " +
|
||||
// txId +
|
||||
// " confirmed in round " +
|
||||
// confirmedTxn["confirmed-round"]
|
||||
// );
|
||||
// console.log("Confirmation response:", confirmedTxn);
|
||||
// let mytxinfo = JSON.stringify(confirmedTxn.txn.txn, undefined, 2);
|
||||
// console.log("Transaction information: %o", mytxinfo);
|
||||
// let string = new TextDecoder().decode(confirmedTxn.txn.txn.note);
|
||||
// console.log("Note field: ", string);
|
||||
let accountInfo = await algodClient.accountInformation(newAcct.addr).do();
|
||||
// console.log(
|
||||
// "Transaction Amount: %d microAlgos",
|
||||
// confirmedTxn.txn.txn.amt
|
||||
// );
|
||||
// console.log("Transaction Fee: %d microAlgos", confirmedTxn.txn.txn.fee);
|
||||
// console.log("Account balance: %d microAlgos", accountInfo.amount);
|
||||
retval.push(newAcct);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
export function createAccount(): Account | undefined {
|
||||
try {
|
||||
const retval = algosdk.generateAccount();
|
||||
// let retval = new Account(tempAcct.addr, Buffer.from(tempAcct.sk));
|
||||
// let account_mnemonic = algosdk.secretKeyToMnemonic(tempAcct.sk);
|
||||
// console.log("Account Address = " + retval.addr);
|
||||
// console.log("Account Mnemonic = " + account_mnemonic);
|
||||
// console.log("Class Account:", retval);
|
||||
|
||||
return retval;
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the balances of all assets for the supplied account
|
||||
* @param client An Algodv2 client
|
||||
* @param account The account containing assets
|
||||
* @returns Map of asset index to qty
|
||||
*/
|
||||
export async function getBalances(
|
||||
client: Algodv2,
|
||||
account: string
|
||||
): Promise<Map<number, number>> {
|
||||
let balances = new Map<number, number>();
|
||||
const accountInfo = await client.accountInformation(account).do();
|
||||
console.log("Account Info:", accountInfo);
|
||||
console.log("Account Info|created-assets:", accountInfo["created-assets"]);
|
||||
|
||||
// Put the algo balance in key 0
|
||||
balances.set(0, accountInfo.amount);
|
||||
|
||||
const assets: Array<any> = accountInfo.assets;
|
||||
console.log("assets", assets);
|
||||
assets.forEach(function (asset) {
|
||||
console.log("inside foreach", asset);
|
||||
const assetId = asset["asset-id"];
|
||||
const amount = asset.amount;
|
||||
balances.set(assetId, amount);
|
||||
});
|
||||
console.log("balances", balances);
|
||||
return balances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the balance of the supplied asset index for the supplied account
|
||||
* @param client An Algodv2 client
|
||||
* @param account The account to query for the supplied asset index
|
||||
* @param assetId The asset index
|
||||
* @returns The quantity of the asset in the supplied account
|
||||
*/
|
||||
export async function getBalance(
|
||||
client: Algodv2,
|
||||
account: string,
|
||||
assetId: bigint
|
||||
): Promise<bigint> {
|
||||
const accountInfo = await client.accountInformation(account).do();
|
||||
|
||||
if (assetId === BigInt(0)) {
|
||||
return accountInfo.amount;
|
||||
}
|
||||
|
||||
let ret = BigInt(0);
|
||||
const assets: Array<any> = accountInfo.assets;
|
||||
assets.forEach((asset) => {
|
||||
if (Number(assetId) === asset["asset-id"]) {
|
||||
ret = asset.amount;
|
||||
return;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
export async function createAsset(account: Account): Promise<any> {
|
||||
console.log("Creating asset...");
|
||||
const aClient = getAlgoClient();
|
||||
const params = await aClient.getTransactionParams().do();
|
||||
const note = undefined; // arbitrary data to be stored in the transaction; here, none is stored
|
||||
// Asset creation specific parameters
|
||||
const addr = account.addr;
|
||||
// Whether user accounts will need to be unfrozen before transacting
|
||||
const defaultFrozen = false;
|
||||
// integer number of decimals for asset unit calculation
|
||||
const decimals = 10;
|
||||
// total number of this asset available for circulation
|
||||
const totalIssuance = 1000000;
|
||||
// Used to display asset units to user
|
||||
const unitName = "NORIUM";
|
||||
// Friendly name of the asset
|
||||
const assetName = "ChuckNorium";
|
||||
// Optional string pointing to a URL relating to the asset
|
||||
// const assetURL = "http://www.chucknorris.com";
|
||||
const assetURL = "";
|
||||
// Optional hash commitment of some sort relating to the asset. 32 character length.
|
||||
// const assetMetadataHash = "16efaa3924a6fd9d3a4824799a4ac65d";
|
||||
const assetMetadataHash = "";
|
||||
// The following parameters are the only ones
|
||||
// that can be changed, and they have to be changed
|
||||
// by the current manager
|
||||
// Specified address can change reserve, freeze, clawback, and manager
|
||||
const manager = account.addr;
|
||||
// Specified address is considered the asset reserve
|
||||
// (it has no special privileges, this is only informational)
|
||||
const reserve = account.addr;
|
||||
// Specified address can freeze or unfreeze user asset holdings
|
||||
const freeze = account.addr;
|
||||
// Specified address can revoke user asset holdings and send
|
||||
// them to other addresses
|
||||
const clawback = account.addr;
|
||||
|
||||
// signing and sending "txn" allows "addr" to create an asset
|
||||
const txn = algosdk.makeAssetCreateTxnWithSuggestedParams(
|
||||
addr,
|
||||
note,
|
||||
totalIssuance,
|
||||
decimals,
|
||||
defaultFrozen,
|
||||
manager,
|
||||
reserve,
|
||||
freeze,
|
||||
clawback,
|
||||
unitName,
|
||||
assetName,
|
||||
assetURL,
|
||||
assetMetadataHash,
|
||||
params
|
||||
);
|
||||
|
||||
const rawSignedTxn = txn.signTxn(account.sk);
|
||||
// console.log("rawSignedTxn:", rawSignedTxn);
|
||||
const tx = await aClient.sendRawTransaction(rawSignedTxn).do();
|
||||
|
||||
// wait for transaction to be confirmed
|
||||
const ptx = await algosdk.waitForConfirmation(aClient, tx.txId, 4);
|
||||
// console.log("createAsset() - ptx:", ptx);
|
||||
// Get the new asset's information from the creator account
|
||||
const assetID: number = ptx["asset-index"];
|
||||
//Get the completed Transaction
|
||||
console.log(
|
||||
"createAsset() - Transaction " +
|
||||
tx.txId +
|
||||
" confirmed in round " +
|
||||
ptx["confirmed-round"]
|
||||
);
|
||||
return assetID;
|
||||
}
|
||||
|
||||
export async function createNFT(account: Account): Promise<number> {
|
||||
console.log("Creating NFT...");
|
||||
const aClient = getAlgoClient();
|
||||
const params = await aClient.getTransactionParams().do();
|
||||
// Asset creation specific parameters
|
||||
const addr = account.addr;
|
||||
// Whether user accounts will need to be unfrozen before transacting
|
||||
const defaultFrozen = false;
|
||||
// integer number of decimals for asset unit calculation
|
||||
const decimals = 0;
|
||||
// total number of this asset available for circulation
|
||||
const total = 1;
|
||||
// Used to display asset units to user
|
||||
const unitName = "CNART";
|
||||
// Friendly name of the asset
|
||||
const assetName = "ChuckNoriumArtwork@arc3";
|
||||
// Optional string pointing to a URL relating to the asset
|
||||
const assetURL = "http://www.chucknorris.com";
|
||||
// Optional hash commitment of some sort relating to the asset. 32 character length.
|
||||
const assetMetadataHash = "16efaa3924a6fd9d3a4824799a4ac65d";
|
||||
// The following parameters are the only ones
|
||||
// that can be changed, and they have to be changed
|
||||
// by the current manager
|
||||
// Specified address can change reserve, freeze, clawback, and manager
|
||||
const manager = account.addr;
|
||||
// Specified address is considered the asset reserve
|
||||
// (it has no special privileges, this is only informational)
|
||||
const reserve = account.addr;
|
||||
// Specified address can freeze or unfreeze user asset holdings
|
||||
const freeze = account.addr;
|
||||
// Specified address can revoke user asset holdings and send
|
||||
// them to other addresses
|
||||
const clawback = account.addr;
|
||||
|
||||
// signing and sending "txn" allows "addr" to create an asset
|
||||
const txn = algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject({
|
||||
from: addr,
|
||||
total,
|
||||
decimals,
|
||||
assetName,
|
||||
unitName,
|
||||
assetURL,
|
||||
assetMetadataHash,
|
||||
defaultFrozen,
|
||||
freeze,
|
||||
manager,
|
||||
clawback,
|
||||
reserve,
|
||||
suggestedParams: params,
|
||||
});
|
||||
|
||||
const rawSignedTxn = txn.signTxn(account.sk);
|
||||
// console.log("rawSignedTxn:", rawSignedTxn);
|
||||
const tx = await aClient.sendRawTransaction(rawSignedTxn).do();
|
||||
|
||||
// wait for transaction to be confirmed
|
||||
const ptx = await algosdk.waitForConfirmation(aClient, tx.txId, 4);
|
||||
// Get the new asset's information from the creator account
|
||||
const assetID: number = ptx["asset-index"];
|
||||
console.log(
|
||||
"createNFT() - Transaction " +
|
||||
tx.txId +
|
||||
" confirmed in round " +
|
||||
ptx["confirmed-round"]
|
||||
);
|
||||
return assetID;
|
||||
}
|
||||
|
||||
export async function getForeignAssetFromVaaAlgorand(
|
||||
client: Algodv2,
|
||||
tokenBridgeId: bigint,
|
||||
vaa: Uint8Array
|
||||
): Promise<bigint | null> {
|
||||
const parsedVAA = _parseVAAAlgorand(vaa);
|
||||
if (parsedVAA.Contract === undefined) {
|
||||
throw "parsedVAA.Contract is undefined";
|
||||
}
|
||||
return await getForeignAssetAlgorand(
|
||||
client,
|
||||
tokenBridgeId,
|
||||
parsedVAA.FromChain as ChainId,
|
||||
parsedVAA.Contract
|
||||
);
|
||||
}
|
||||
|
||||
export async function signSendAndConfirmAlgorand(
|
||||
algodClient: Algodv2,
|
||||
txs: TransactionSignerPair[],
|
||||
wallet: Account
|
||||
) {
|
||||
assignGroupID(txs.map((tx) => tx.tx));
|
||||
const signedTxns: Uint8Array[] = [];
|
||||
for (const tx of txs) {
|
||||
if (tx.signer) {
|
||||
signedTxns.push(await tx.signer.signTxn(tx.tx));
|
||||
} else {
|
||||
signedTxns.push(tx.tx.signTxn(wallet.sk));
|
||||
}
|
||||
}
|
||||
await algodClient.sendRawTransaction(signedTxns).do();
|
||||
const result = await waitForConfirmation(
|
||||
algodClient,
|
||||
txs[txs.length - 1].tx.txID(),
|
||||
1
|
||||
);
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
cd ../..
|
||||
DOCKER_BUILDKIT=1 docker build --target go-export -f Dockerfile.proto -o type=local,dest=node .
|
||||
DOCKER_BUILDKIT=1 docker build --target node-export -f Dockerfile.proto -o type=local,dest=. .
|
||||
cd node/
|
||||
echo "Have patience, this step takes upwards of 500 seconds!"
|
||||
if [ $(uname -m) = "arm64" ]; then
|
||||
echo "Building Guardian for linux/amd64"
|
||||
DOCKER_BUILDKIT=1 docker build --platform linux/amd64 -f Dockerfile -t guardian .
|
||||
else
|
||||
echo "Building Guardian natively"
|
||||
DOCKER_BUILDKIT=1 docker build -f Dockerfile -t guardian .
|
||||
fi
|
|
@ -0,0 +1,112 @@
|
|||
const nearAPI = require("near-api-js");
|
||||
const BN = require("bn.js");
|
||||
const bs58 = require("bs58");
|
||||
const fs = require("fs");
|
||||
const fetch = require("node-fetch");
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
|
||||
function getConfig(env: any) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
wormholeAccount: "wormhole.test.near",
|
||||
tokenAccount: "token.test.near",
|
||||
userAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async function testDeploy() {
|
||||
let config = getConfig(process.env.NEAR_ENV || "sandbox");
|
||||
|
||||
// Retrieve the validator key directly in the Tilt environment
|
||||
const response = await fetch("http://localhost:3031/validator_key.json");
|
||||
|
||||
const keyFile = await response.json();
|
||||
|
||||
let masterKey = nearAPI.utils.KeyPair.fromString(
|
||||
keyFile.secret_key || keyFile.private_key
|
||||
);
|
||||
let masterPubKey = masterKey.getPublicKey();
|
||||
|
||||
let keyStore = new nearAPI.keyStores.InMemoryKeyStore();
|
||||
keyStore.setKey(config.networkId, config.masterAccount, masterKey);
|
||||
|
||||
let near = await nearAPI.connect({
|
||||
deps: {
|
||||
keyStore,
|
||||
},
|
||||
networkId: config.networkId,
|
||||
nodeUrl: config.nodeUrl,
|
||||
});
|
||||
let masterAccount = new nearAPI.Account(
|
||||
near.connection,
|
||||
config.masterAccount
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR masterAccount: " +
|
||||
JSON.stringify(await masterAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
let userKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(config.networkId, config.userAccount, userKey);
|
||||
|
||||
console.log(
|
||||
"creating a user account: " +
|
||||
config.userAccount +
|
||||
" with key " +
|
||||
userKey.getPublicKey()
|
||||
);
|
||||
|
||||
await masterAccount.createAccount(
|
||||
config.userAccount,
|
||||
userKey.getPublicKey(),
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
const userAccount = new nearAPI.Account(near.connection, config.userAccount);
|
||||
|
||||
const wormholeContract = await fs.readFileSync(
|
||||
"../contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm"
|
||||
);
|
||||
|
||||
//console.log("sending money to cover the cost of deploying this contract.. so that we fail for the right reasons");
|
||||
//await userAccount.sendMoney(config.wormholeAccount, new BN("10000000000000000000000000"));
|
||||
|
||||
keyStore.setKey(config.networkId, config.wormholeAccount, masterKey);
|
||||
|
||||
let wormholeAccount = new nearAPI.Account(
|
||||
near.connection,
|
||||
config.wormholeAccount
|
||||
);
|
||||
|
||||
try {
|
||||
console.log("redeploying wormhole contract using standard deployment API");
|
||||
let resp = await wormholeAccount.deployContract(wormholeContract);
|
||||
console.log(resp);
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. nice.. we dont suck");
|
||||
}
|
||||
|
||||
console.log("calling upgradeContract");
|
||||
let result = await userAccount.functionCall({
|
||||
contractId: config.wormholeAccount,
|
||||
methodName: "update_contract",
|
||||
args: wormholeContract,
|
||||
attachedDeposit: "12500000000000000000000",
|
||||
gas: 300000000000000,
|
||||
});
|
||||
console.log("done");
|
||||
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
testDeploy();
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/certusone/wormhole/near
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/tidwall/gjson v1.14.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
|
@ -0,0 +1,39 @@
|
|||
let elliptic = require('elliptic');
|
||||
const web3Utils = require("web3-utils");
|
||||
|
||||
const guardianKey = "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe";
|
||||
|
||||
const guardianPrivKeys = "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
|
||||
|
||||
let ec = new elliptic.ec('secp256k1');
|
||||
let keyPair = ec.keyFromPrivate(guardianPrivKeys);
|
||||
let privKey = keyPair.getPrivate("hex");
|
||||
let pubKey = keyPair.getPublic();
|
||||
|
||||
console.log(`Private key: ${privKey}`);
|
||||
|
||||
console.log("Public key :", pubKey.encode("hex"))
|
||||
//console.log("Public key (compressed):", pubKey.encodeCompressed("hex"));
|
||||
|
||||
let msg = 'Message for signing';
|
||||
let msgHash = web3Utils.keccak256(msg).substr(2);
|
||||
let signature = keyPair.sign(msgHash, { canonical: true });
|
||||
console.log(`Msg: ${msg}`);
|
||||
console.log(`Msg hash: ${msgHash}`);
|
||||
console.log("Signature:", signature);
|
||||
|
||||
console.log();
|
||||
|
||||
let hexToDecimal = (x) => ec.keyFromPrivate(x, "hex").getPrivate().toString(10);
|
||||
let pubKeyRecovered = ec.recoverPubKey(hexToDecimal(msgHash), signature, signature.recoveryParam, "hex");
|
||||
|
||||
pk = pubKeyRecovered.encode("hex")
|
||||
|
||||
console.log("Public key :", pubKey.encode("hex").substring(2, 130));
|
||||
console.log("Recovered pubKey:", pk);
|
||||
|
||||
console.log("keccak256 :", web3Utils.keccak256("0x" + pk.substring(2, 130)).substring(26))
|
||||
console.log("guardianKey:", guardianKey);
|
||||
|
||||
let validSig = ec.verify(msgHash, signature, pubKeyRecovered);
|
||||
console.log("Signature valid?", validSig);
|
|
@ -0,0 +1,91 @@
|
|||
// npx pretty-quick
|
||||
|
||||
import Web3 from "web3";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
|
||||
import { TestLib } from "./testlib";
|
||||
|
||||
import { Account as nearAccount } from "@certusone/wormhole-sdk/node_modules/near-api-js";
|
||||
const BN = require("bn.js");
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
const nearAPI = require("near-api-js");
|
||||
|
||||
export function textToHexString(name: string): string {
|
||||
return Buffer.from(name, "binary").toString("hex");
|
||||
}
|
||||
|
||||
export const hexToUint8Array = (h: string): Uint8Array =>
|
||||
new Uint8Array(Buffer.from(h, "hex"));
|
||||
|
||||
export const uint8ArrayToHex = (a: Uint8Array): string =>
|
||||
Buffer.from(a).toString("hex");
|
||||
|
||||
function getConfig(env: any) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
wormholeAccount: "wormhole.test.near",
|
||||
tokenAccount: "token.test.near",
|
||||
nftAccount: "nft.test.near",
|
||||
userAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
user2Account:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async function testNearMsg() {
|
||||
let config = getConfig(process.env.NEAR_ENV || "sandbox");
|
||||
|
||||
// Retrieve the validator key directly in the Tilt environment
|
||||
const response = await fetch("http://localhost:3031/validator_key.json");
|
||||
|
||||
const keyFile = await response.json();
|
||||
|
||||
let masterKey = nearAPI.utils.KeyPair.fromString(
|
||||
keyFile.secret_key || keyFile.private_key
|
||||
);
|
||||
let masterPubKey = masterKey.getPublicKey();
|
||||
|
||||
let keyStore = new nearAPI.keyStores.InMemoryKeyStore();
|
||||
keyStore.setKey(config.networkId, config.masterAccount, masterKey);
|
||||
|
||||
let near = await nearAPI.connect({
|
||||
deps: {
|
||||
keyStore,
|
||||
},
|
||||
networkId: config.networkId,
|
||||
nodeUrl: config.nodeUrl,
|
||||
});
|
||||
let masterAccount = new nearAPI.Account(
|
||||
near.connection,
|
||||
config.masterAccount
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR masterAccount: " +
|
||||
JSON.stringify(await masterAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
while (true) {
|
||||
let myAddress = nearAPI.providers.getTransactionLastResult(
|
||||
await masterAccount.functionCall({
|
||||
contractId: "test.test.near",
|
||||
methodName: "publish_message",
|
||||
args: { core: "wormhole.test.near", p: "00" },
|
||||
gas: new BN("100000000000000"),
|
||||
attachedDeposit: new BN("2000000000000000000000"), // 0.002 NEAR
|
||||
})
|
||||
);
|
||||
await new Promise((f) => setTimeout(f, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
testNearMsg();
|
|
@ -0,0 +1,350 @@
|
|||
// npx pretty-quick
|
||||
|
||||
import Web3 from "web3";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
|
||||
import { TestLib } from "./testlib";
|
||||
|
||||
import {
|
||||
NFTImplementation,
|
||||
NFTImplementation__factory,
|
||||
nft_bridge,
|
||||
getEmitterAddressEth,
|
||||
parseSequenceFromLogEth,
|
||||
getSignedVAAWithRetry,
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_NEAR,
|
||||
ChainId,
|
||||
ChainName,
|
||||
textToUint8Array,
|
||||
tryNativeToUint8Array,
|
||||
CONTRACTS,
|
||||
parseNFTPayload,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import {
|
||||
ParsedVAA,
|
||||
_parseVAAAlgorand,
|
||||
_parseNFTAlgorand,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/algorand";
|
||||
|
||||
import {
|
||||
BigNumberish,
|
||||
ethers,
|
||||
} from "@certusone/wormhole-sdk/node_modules/ethers";
|
||||
|
||||
import { Account as nearAccount } from "@certusone/wormhole-sdk/node_modules/near-api-js";
|
||||
const BN = require("bn.js");
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
const ERC721 = require("@openzeppelin/contracts/build/contracts/ERC721PresetMinterPauserAutoId.json");
|
||||
const nearAPI = require("near-api-js");
|
||||
|
||||
export const ETH_NODE_URL = "ws://localhost:8545";
|
||||
export const ETH_PRIVATE_KEY =
|
||||
"0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1"; // account 1
|
||||
export const ETH_CORE_BRIDGE_ADDRESS =
|
||||
"0xC89Ce4735882C9F0f0FE26686c53074E09B0D550";
|
||||
export const ETH_NFT_BRIDGE_ADDRESS =
|
||||
"0x26b4afb60d6c903165150c6f0aa14f8016be4aec";
|
||||
|
||||
const web3 = new Web3(ETH_NODE_URL);
|
||||
let provider: ethers.providers.WebSocketProvider =
|
||||
new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
let signer: ethers.Wallet = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||
|
||||
export function textToHexString(name: string): string {
|
||||
return Buffer.from(name, "binary").toString("hex");
|
||||
}
|
||||
|
||||
export const hexToUint8Array = (h: string): Uint8Array =>
|
||||
new Uint8Array(Buffer.from(h, "hex"));
|
||||
|
||||
export const uint8ArrayToHex = (a: Uint8Array): string =>
|
||||
Buffer.from(a).toString("hex");
|
||||
|
||||
async function _transferFromEth(
|
||||
erc721: string,
|
||||
token_id: BigNumberish,
|
||||
address: string,
|
||||
chain: ChainId
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
return nft_bridge.transferFromEth(
|
||||
ETH_NFT_BRIDGE_ADDRESS,
|
||||
signer,
|
||||
erc721,
|
||||
token_id,
|
||||
chain,
|
||||
hexToUint8Array(address)
|
||||
);
|
||||
}
|
||||
|
||||
async function deployNFTOnEth(
|
||||
name: string,
|
||||
symbol: string,
|
||||
uri: string,
|
||||
how_many: number
|
||||
): Promise<NFTImplementation> {
|
||||
const accounts = await web3.eth.getAccounts();
|
||||
const nftContract = new web3.eth.Contract(ERC721.abi);
|
||||
let nft = await nftContract
|
||||
.deploy({
|
||||
data: ERC721.bytecode,
|
||||
arguments: [name, symbol, uri],
|
||||
})
|
||||
.send({
|
||||
from: accounts[1],
|
||||
gas: 5000000,
|
||||
});
|
||||
|
||||
// The eth contracts mints tokens with sequential ids, so in order to get to a
|
||||
// specific id, we need to mint multiple nfts. We need this to test that
|
||||
// foreign ids on terra get converted to the decimal stringified form of the
|
||||
// original id.
|
||||
for (var i = 0; i < how_many; i++) {
|
||||
await nft.methods.mint(accounts[1]).send({
|
||||
from: accounts[1],
|
||||
gas: 1000000,
|
||||
});
|
||||
}
|
||||
|
||||
return NFTImplementation__factory.connect(nft.options.address, signer);
|
||||
}
|
||||
|
||||
async function waitUntilEthTxObserved(
|
||||
receipt: ethers.ContractReceipt
|
||||
): Promise<Uint8Array> {
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
let sequence = parseSequenceFromLogEth(receipt, ETH_CORE_BRIDGE_ADDRESS);
|
||||
let emitterAddress = getEmitterAddressEth(ETH_NFT_BRIDGE_ADDRESS);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
return signedVAA;
|
||||
}
|
||||
|
||||
function getConfig(env: any) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
wormholeAccount: "wormhole.test.near",
|
||||
tokenAccount: "token.test.near",
|
||||
nftAccount: "nft.test.near",
|
||||
userAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
user2Account:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export const METADATA_REPLACE = new RegExp("\u0000", "g");
|
||||
|
||||
async function testNearSDK() {
|
||||
let config = getConfig(process.env.NEAR_ENV || "sandbox");
|
||||
|
||||
// Retrieve the validator key directly in the Tilt environment
|
||||
const response = await fetch("http://localhost:3031/validator_key.json");
|
||||
|
||||
const keyFile = await response.json();
|
||||
|
||||
let masterKey = nearAPI.utils.KeyPair.fromString(
|
||||
keyFile.secret_key || keyFile.private_key
|
||||
);
|
||||
let masterPubKey = masterKey.getPublicKey();
|
||||
|
||||
let keyStore = new nearAPI.keyStores.InMemoryKeyStore();
|
||||
keyStore.setKey(config.networkId, config.masterAccount, masterKey);
|
||||
|
||||
let near = await nearAPI.connect({
|
||||
deps: {
|
||||
keyStore,
|
||||
},
|
||||
networkId: config.networkId,
|
||||
nodeUrl: config.nodeUrl,
|
||||
});
|
||||
let masterAccount = new nearAPI.Account(
|
||||
near.connection,
|
||||
config.masterAccount
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR masterAccount: " +
|
||||
JSON.stringify(await masterAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
let userKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(config.networkId, config.userAccount, userKey);
|
||||
let user2Key = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(config.networkId, config.user2Account, user2Key);
|
||||
|
||||
console.log(
|
||||
"creating a user account: " +
|
||||
config.userAccount +
|
||||
" with key " +
|
||||
userKey.getPublicKey()
|
||||
);
|
||||
|
||||
await masterAccount.createAccount(
|
||||
config.userAccount,
|
||||
userKey.getPublicKey(),
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
const userAccount = new nearAPI.Account(near.connection, config.userAccount);
|
||||
|
||||
console.log(
|
||||
"Creating new random non-wormhole nft and air dropping it to myself"
|
||||
);
|
||||
|
||||
let randoNFT = nearAPI.providers.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: "test.test.near",
|
||||
methodName: "deploy_nft",
|
||||
args: {
|
||||
account: userAccount.accountId,
|
||||
},
|
||||
attachedDeposit: new BN("10000000000000000000000000"),
|
||||
gas: 300000000000000,
|
||||
})
|
||||
);
|
||||
|
||||
console.log(randoNFT);
|
||||
|
||||
console.log("Registering the user");
|
||||
|
||||
let myAddress = nearAPI.providers.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: config.nftAccount,
|
||||
methodName: "register_account",
|
||||
args: { account: userAccount.accountId },
|
||||
gas: new BN("100000000000000"),
|
||||
attachedDeposit: new BN("2000000000000000000000"), // 0.002 NEAR
|
||||
})
|
||||
);
|
||||
console.log("myAddress: " + myAddress);
|
||||
|
||||
let contract = textToHexString(Math.random().toString());
|
||||
let tokenid = textToHexString(Math.random().toString());
|
||||
|
||||
let ts = new TestLib();
|
||||
let seq = Math.floor(new Date().getTime() / 1000);
|
||||
let v = ts.genNFTTransfer(
|
||||
ts.singleGuardianPrivKey,
|
||||
0,
|
||||
1,
|
||||
seq,
|
||||
|
||||
contract,
|
||||
1, // from chain
|
||||
|
||||
"George", // symbol
|
||||
"GeorgesNFT", // name
|
||||
tokenid,
|
||||
"https://cloudflare-ipfs.com/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/10",
|
||||
myAddress,
|
||||
15
|
||||
);
|
||||
|
||||
//let tvaa = ts.hexStringToUint8Array(v);
|
||||
//console.log(v);
|
||||
//console.log(_parseNFTAlgorand(tvaa));
|
||||
|
||||
let res = await userAccount.viewFunction(
|
||||
config.nftAccount,
|
||||
"deposit_estimates",
|
||||
{}
|
||||
);
|
||||
|
||||
console.log(res);
|
||||
|
||||
let emitter = (await userAccount.viewFunction(
|
||||
config.nftAccount,
|
||||
"emitter",
|
||||
{}
|
||||
))[1];
|
||||
|
||||
console.log(emitter);
|
||||
|
||||
console.log("submitting a nft");
|
||||
|
||||
let ret = nearAPI.providers.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: config.nftAccount,
|
||||
methodName: "submit_vaa",
|
||||
args: {
|
||||
vaa: v,
|
||||
},
|
||||
gas: 300000000000000,
|
||||
attachedDeposit: new BN(res[1]),
|
||||
})
|
||||
);
|
||||
|
||||
console.log(ret);
|
||||
|
||||
ret = nearAPI.providers.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: config.nftAccount,
|
||||
methodName: "submit_vaa",
|
||||
args: {
|
||||
vaa: v,
|
||||
},
|
||||
gas: 300000000000000,
|
||||
attachedDeposit: new BN(res[1]),
|
||||
})
|
||||
);
|
||||
|
||||
console.log(ret);
|
||||
|
||||
console.log("make it go away");
|
||||
|
||||
let t = nearAPI.providers.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: config.nftAccount,
|
||||
methodName: "initiate_transfer",
|
||||
args: {
|
||||
asset: ret[0],
|
||||
token_id: ret[1],
|
||||
recipient_chain: 8,
|
||||
recipient: "00112233",
|
||||
nonce: 5,
|
||||
},
|
||||
gas: 300000000000000,
|
||||
attachedDeposit: 1,
|
||||
})
|
||||
);
|
||||
|
||||
console.log(t);
|
||||
|
||||
console.log(emitter);
|
||||
|
||||
console.log("looking it up");
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
emitter,
|
||||
t,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log(signedVAA);
|
||||
|
||||
console.log("all done");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testNearSDK();
|
|
@ -0,0 +1,450 @@
|
|||
// npx pretty-quick
|
||||
|
||||
const sha256 = require("js-sha256");
|
||||
const nearAPI = require("near-api-js");
|
||||
const fs = require("fs").promises;
|
||||
const assert = require("assert").strict;
|
||||
const fetch = require("node-fetch");
|
||||
const elliptic = require("elliptic");
|
||||
const web3Utils = require("web3-utils");
|
||||
import { zeroPad } from "@ethersproject/bytes";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
|
||||
import { Account as nearAccount } from "@certusone/wormhole-sdk/node_modules/near-api-js";
|
||||
const BN = require("bn.js");
|
||||
|
||||
import { TestLib } from "./testlib";
|
||||
|
||||
import algosdk, {
|
||||
Account,
|
||||
Algodv2,
|
||||
OnApplicationComplete,
|
||||
SuggestedParams,
|
||||
bigIntToBytes,
|
||||
decodeAddress,
|
||||
getApplicationAddress,
|
||||
makeApplicationCallTxnFromObject,
|
||||
makePaymentTxnWithSuggestedParamsFromObject,
|
||||
waitForConfirmation,
|
||||
} from "@certusone/wormhole-sdk/node_modules/algosdk";
|
||||
|
||||
import {
|
||||
createAsset,
|
||||
getAlgoClient,
|
||||
getBalance,
|
||||
getBalances,
|
||||
getForeignAssetFromVaaAlgorand,
|
||||
getTempAccounts,
|
||||
signSendAndConfirmAlgorand,
|
||||
} from "./algoHelpers";
|
||||
|
||||
import {
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_NEAR,
|
||||
ChainId,
|
||||
ChainName,
|
||||
textToHexString,
|
||||
textToUint8Array,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/utils";
|
||||
|
||||
import { safeBigIntToNumber } from "@certusone/wormhole-sdk/lib/cjs/utils/bigint";
|
||||
|
||||
import {
|
||||
CONTRACTS,
|
||||
attestNearFromNear,
|
||||
attestTokenFromNear,
|
||||
attestFromAlgorand,
|
||||
createWrappedOnAlgorand,
|
||||
createWrappedOnNear,
|
||||
getEmitterAddressAlgorand,
|
||||
getForeignAssetAlgorand,
|
||||
getForeignAssetNear,
|
||||
getIsTransferCompletedNear,
|
||||
getIsWrappedAssetNear,
|
||||
getOriginalAssetNear,
|
||||
getSignedVAAWithRetry,
|
||||
redeemOnAlgorand,
|
||||
redeemOnNear,
|
||||
transferFromAlgorand,
|
||||
transferNearFromNear,
|
||||
transferTokenFromNear,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
const wh = require("@certusone/wormhole-sdk");
|
||||
|
||||
import { parseSequenceFromLogAlgorand } from "@certusone/wormhole-sdk/lib/cjs/bridge";
|
||||
|
||||
import {
|
||||
getMessageFee,
|
||||
optin,
|
||||
TransactionSignerPair,
|
||||
_parseVAAAlgorand,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/algorand";
|
||||
|
||||
export const uint8ArrayToHex = (a: Uint8Array): string =>
|
||||
Buffer.from(a).toString("hex");
|
||||
|
||||
export const hexToUint8Array = (h: string): Uint8Array =>
|
||||
new Uint8Array(Buffer.from(h, "hex"));
|
||||
|
||||
function getConfig(env: any) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
wormholeAccount: "wormhole.test.near",
|
||||
tokenAccount: "token.test.near",
|
||||
userAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
user2Account:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export function logNearGas(result: any, comment: string) {
|
||||
const { totalGasBurned, totalTokensBurned } = result.receipts_outcome.reduce(
|
||||
(acc: any, receipt: any) => {
|
||||
acc.totalGasBurned += receipt.outcome.gas_burnt;
|
||||
acc.totalTokensBurned += nearAPI.utils.format.formatNearAmount(
|
||||
receipt.outcome.tokens_burnt
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
totalGasBurned: result.transaction_outcome.outcome.gas_burnt,
|
||||
totalTokensBurned: nearAPI.utils.format.formatNearAmount(
|
||||
result.transaction_outcome.outcome.tokens_burnt
|
||||
),
|
||||
}
|
||||
);
|
||||
console.log(
|
||||
comment,
|
||||
"totalGasBurned",
|
||||
totalGasBurned,
|
||||
"totalTokensBurned",
|
||||
totalTokensBurned
|
||||
);
|
||||
}
|
||||
|
||||
export function parseSequenceFromLogNear(result: any): [number, string] {
|
||||
let sequence = "";
|
||||
for (const o of result.receipts_outcome) {
|
||||
for (const l of o.outcome.logs) {
|
||||
if (l.startsWith("EVENT_JSON:")) {
|
||||
const body = JSON.parse(l.slice(11));
|
||||
if (body.standard == "wormhole" && body.event == "publish") {
|
||||
return [body.seq, body.emitter];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [-1, ""];
|
||||
}
|
||||
|
||||
async function testNearSDK() {
|
||||
let config = getConfig(process.env.NEAR_ENV || "sandbox");
|
||||
|
||||
// Retrieve the validator key directly in the Tilt environment
|
||||
const response = await fetch("http://localhost:3031/validator_key.json");
|
||||
|
||||
const keyFile = await response.json();
|
||||
|
||||
let masterKey = nearAPI.utils.KeyPair.fromString(
|
||||
keyFile.secret_key || keyFile.private_key
|
||||
);
|
||||
let masterPubKey = masterKey.getPublicKey();
|
||||
|
||||
let keyStore = new nearAPI.keyStores.InMemoryKeyStore();
|
||||
keyStore.setKey(config.networkId, config.masterAccount, masterKey);
|
||||
|
||||
let near = await nearAPI.connect({
|
||||
deps: {
|
||||
keyStore,
|
||||
},
|
||||
networkId: config.networkId,
|
||||
nodeUrl: config.nodeUrl,
|
||||
});
|
||||
let masterAccount = new nearAPI.Account(
|
||||
near.connection,
|
||||
config.masterAccount
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR masterAccount: " +
|
||||
JSON.stringify(await masterAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
let userKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(config.networkId, config.userAccount, userKey);
|
||||
let user2Key = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(config.networkId, config.user2Account, user2Key);
|
||||
|
||||
console.log(
|
||||
"creating a user account: " +
|
||||
config.userAccount +
|
||||
" with key " +
|
||||
userKey.getPublicKey()
|
||||
);
|
||||
|
||||
await masterAccount.createAccount(
|
||||
config.userAccount,
|
||||
userKey.getPublicKey(),
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
const userAccount = new nearAPI.Account(near.connection, config.userAccount);
|
||||
|
||||
console.log(
|
||||
"creating a second user account: " +
|
||||
config.user2Account +
|
||||
" with key " +
|
||||
user2Key.getPublicKey()
|
||||
);
|
||||
|
||||
await masterAccount.createAccount(
|
||||
config.user2Account,
|
||||
user2Key.getPublicKey(),
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
const user2Account = new nearAPI.Account(
|
||||
near.connection,
|
||||
config.user2Account
|
||||
);
|
||||
|
||||
let token_bridge = CONTRACTS.DEVNET.near.token_bridge;
|
||||
let core_bridge = CONTRACTS.DEVNET.near.core;
|
||||
|
||||
console.log("Setting up algorand wallet");
|
||||
|
||||
let algoCore = BigInt(CONTRACTS.DEVNET.algorand.core);
|
||||
let algoToken = BigInt(CONTRACTS.DEVNET.algorand.token_bridge);
|
||||
|
||||
const tbAddr: string = getApplicationAddress(algoToken);
|
||||
const decTbAddr: Uint8Array = decodeAddress(tbAddr).publicKey;
|
||||
const aa: string = uint8ArrayToHex(decTbAddr);
|
||||
|
||||
const algoClient: algosdk.Algodv2 = getAlgoClient();
|
||||
const tempAccts: Account[] = await getTempAccounts();
|
||||
const numAccts: number = tempAccts.length;
|
||||
|
||||
const algoWallet: Account = tempAccts[0];
|
||||
|
||||
console.log("Creating USDC on Near");
|
||||
|
||||
let ts = new TestLib();
|
||||
let seq = Math.floor(new Date().getTime() / 1000);
|
||||
let usdcvaa = ts.hexStringToUint8Array(
|
||||
ts.genAssetMeta(
|
||||
ts.singleGuardianPrivKey,
|
||||
0,
|
||||
1,
|
||||
seq,
|
||||
"4523c3F29447d1f32AEa95BEBD00383c4640F1b4".toLowerCase(),
|
||||
1,
|
||||
8,
|
||||
"USDC",
|
||||
"CircleCoin"
|
||||
)
|
||||
);
|
||||
|
||||
seq = seq + 1;
|
||||
|
||||
let usdcp = _parseVAAAlgorand(usdcvaa);
|
||||
|
||||
let usdc = await createWrappedOnNear(userAccount, token_bridge, usdcvaa);
|
||||
console.log(usdc);
|
||||
|
||||
console.log("Creating USDC token on algorand");
|
||||
let tx = await createWrappedOnAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
usdcvaa
|
||||
);
|
||||
await signSendAndConfirmAlgorand(algoClient, tx, algoWallet);
|
||||
|
||||
console.log("Registering the receiving account");
|
||||
|
||||
let myAddress = nearAPI.providers.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: token_bridge,
|
||||
methodName: "register_account",
|
||||
args: { account: userAccount.accountId },
|
||||
gas: new BN("100000000000000"),
|
||||
attachedDeposit: new BN("2000000000000000000000"), // 0.002 NEAR
|
||||
})
|
||||
);
|
||||
console.log("myAddress: " + myAddress);
|
||||
|
||||
console.log("Airdropping USDC on myself");
|
||||
{
|
||||
let trans = ts.genTransfer(
|
||||
ts.singleGuardianPrivKey,
|
||||
0,
|
||||
1,
|
||||
seq,
|
||||
10000,
|
||||
"4523c3F29447d1f32AEa95BEBD00383c4640F1b4".toLowerCase(),
|
||||
1,
|
||||
myAddress, // lets send it to the correct user (use the hash)
|
||||
CHAIN_ID_NEAR,
|
||||
0
|
||||
);
|
||||
console.log(trans);
|
||||
|
||||
console.log(
|
||||
await redeemOnNear(userAccount, token_bridge, hexToUint8Array(trans))
|
||||
);
|
||||
}
|
||||
console.log(".. created some USDC on near");
|
||||
|
||||
let wrappedTransfer;
|
||||
{
|
||||
console.log("transfer USDC from near to algorand");
|
||||
let s = await transferTokenFromNear(
|
||||
userAccount,
|
||||
core_bridge,
|
||||
token_bridge,
|
||||
usdc,
|
||||
BigInt(100),
|
||||
decodeAddress(algoWallet.addr).publicKey,
|
||||
8,
|
||||
BigInt(0)
|
||||
);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
s[1],
|
||||
s[0].toString(),
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
wrappedTransfer = signedVAA;
|
||||
}
|
||||
|
||||
let usdcAssetId;
|
||||
{
|
||||
console.log("redeeming our wrapped USDC from Near on Algorand");
|
||||
const tx = await redeemOnAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
wrappedTransfer,
|
||||
algoWallet.addr
|
||||
);
|
||||
await signSendAndConfirmAlgorand(algoClient, tx, algoWallet);
|
||||
|
||||
let p = _parseVAAAlgorand(wrappedTransfer);
|
||||
usdcAssetId = (await getForeignAssetAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
p.FromChain as ChainId,
|
||||
p.Contract as string
|
||||
)) as bigint;
|
||||
console.log("usdc asset id: " + usdcAssetId);
|
||||
}
|
||||
|
||||
const emitterAddr = getEmitterAddressAlgorand(algoToken);
|
||||
|
||||
console.log("wallet addr: " + algoWallet.addr);
|
||||
console.log("usdcAssetId: " + usdcAssetId);
|
||||
|
||||
console.log("transfering USDC from Algo To Near... getting the vaa");
|
||||
console.log("myAddress: " + myAddress);
|
||||
|
||||
let testAddress = nearAPI.providers.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: token_bridge,
|
||||
methodName: "register_account",
|
||||
args: { account: "test.test.near" },
|
||||
gas: new BN("100000000000000"),
|
||||
attachedDeposit: new BN("2000000000000000000000"), // 0.002 NEAR
|
||||
})
|
||||
);
|
||||
console.log("testAddress: " + testAddress);
|
||||
|
||||
let transferAlgoToNearP3;
|
||||
{
|
||||
const AmountToTransfer: number = 100;
|
||||
const Fee: number = 20;
|
||||
const transferTxs = await transferFromAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
usdcAssetId,
|
||||
BigInt(AmountToTransfer),
|
||||
testAddress,
|
||||
CHAIN_ID_NEAR,
|
||||
BigInt(Fee),
|
||||
hexToUint8Array("ff")
|
||||
);
|
||||
const transferResult = await signSendAndConfirmAlgorand(
|
||||
algoClient,
|
||||
transferTxs,
|
||||
algoWallet
|
||||
);
|
||||
const txSid = parseSequenceFromLogAlgorand(transferResult);
|
||||
transferAlgoToNearP3 = (
|
||||
await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_ALGORAND,
|
||||
emitterAddr,
|
||||
txSid,
|
||||
{ transport: NodeHttpTransport() }
|
||||
)
|
||||
).vaaBytes;
|
||||
}
|
||||
|
||||
console.log("redeeming P3 NEAR on Near");
|
||||
console.log(
|
||||
await redeemOnNear(user2Account, token_bridge, transferAlgoToNearP3)
|
||||
);
|
||||
|
||||
console.log("transfering USDC from Algo To Near... getting the vaa");
|
||||
let transferAlgoToNearRandoP3;
|
||||
{
|
||||
const AmountToTransfer: number = 100;
|
||||
const Fee: number = 20;
|
||||
const transferTxs = await transferFromAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
usdcAssetId,
|
||||
BigInt(AmountToTransfer),
|
||||
testAddress,
|
||||
CHAIN_ID_NEAR,
|
||||
BigInt(Fee),
|
||||
hexToUint8Array("ff")
|
||||
);
|
||||
const transferResult = await signSendAndConfirmAlgorand(
|
||||
algoClient,
|
||||
transferTxs,
|
||||
algoWallet
|
||||
);
|
||||
const txSid = parseSequenceFromLogAlgorand(transferResult);
|
||||
transferAlgoToNearRandoP3 = (
|
||||
await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_ALGORAND,
|
||||
emitterAddr,
|
||||
txSid,
|
||||
{ transport: NodeHttpTransport() }
|
||||
)
|
||||
).vaaBytes;
|
||||
}
|
||||
|
||||
console.log("What next?");
|
||||
}
|
||||
|
||||
testNearSDK();
|
|
@ -0,0 +1,134 @@
|
|||
const nearAPI = require("near-api-js");
|
||||
const BN = require("bn.js");
|
||||
const fs = require("fs").promises;
|
||||
const assert = require("assert").strict;
|
||||
const fetch = require('node-fetch');
|
||||
const elliptic = require("elliptic");
|
||||
const web3Utils = require("web3-utils");
|
||||
|
||||
function getConfig(env: any) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
contractAccount: Math.floor(Math.random() * 10000).toString() + "wormhole2.test.near",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const contractMethods = {
|
||||
viewMethods: [],
|
||||
changeMethods: ["recover_key"],
|
||||
};
|
||||
let config :any;
|
||||
let masterAccount : any;
|
||||
let masterKey : any;
|
||||
let pubKey : any;
|
||||
let keyStore : any;
|
||||
let near : any;
|
||||
|
||||
async function initNear() {
|
||||
config = getConfig(process.env.NEAR_ENV || "sandbox");
|
||||
|
||||
// Retrieve the validator key directly in the Tilt environment
|
||||
const response = await fetch('http://localhost:3031/validator_key.json');
|
||||
const keyFile = await response.json();
|
||||
|
||||
masterKey = nearAPI.utils.KeyPair.fromString(
|
||||
keyFile.secret_key || keyFile.private_key
|
||||
);
|
||||
pubKey = masterKey.getPublicKey();
|
||||
keyStore = new nearAPI.keyStores.InMemoryKeyStore();
|
||||
keyStore.setKey(config.networkId, config.masterAccount, masterKey);
|
||||
near = await nearAPI.connect({
|
||||
deps: {
|
||||
keyStore,
|
||||
},
|
||||
networkId: config.networkId,
|
||||
nodeUrl: config.nodeUrl,
|
||||
});
|
||||
masterAccount = new nearAPI.Account(near.connection, config.masterAccount);
|
||||
console.log("Finish init NEAR: " + JSON.stringify(await masterAccount.getAccountBalance()));
|
||||
}
|
||||
|
||||
async function createContractUser(
|
||||
accountPrefix : any,
|
||||
contractAccountId : any,
|
||||
contractMethod : any
|
||||
) {
|
||||
let accountId = Math.floor(Math.random() * 10000).toString() + accountPrefix + "." + config.masterAccount;
|
||||
|
||||
console.log(accountId);
|
||||
|
||||
let resp = await masterAccount.createAccount(
|
||||
accountId,
|
||||
pubKey,
|
||||
new BN(10).pow(new BN(25))
|
||||
);
|
||||
console.log("accountId: " + JSON.stringify(resp))
|
||||
|
||||
keyStore.setKey(config.networkId, accountId, masterKey);
|
||||
const account = new nearAPI.Account(near.connection, accountId);
|
||||
const accountUseContract = new nearAPI.Contract(
|
||||
account,
|
||||
contractAccountId,
|
||||
contractMethods
|
||||
);
|
||||
return accountUseContract;
|
||||
}
|
||||
|
||||
async function initTest() {
|
||||
const contract = await fs.readFile("./target/wasm32-unknown-unknown/release/near_wormhole.wasm");
|
||||
|
||||
const _contractAccount = await masterAccount.createAndDeployContract(
|
||||
config.contractAccount,
|
||||
pubKey,
|
||||
contract,
|
||||
new BN(10).pow(new BN(26))
|
||||
);
|
||||
|
||||
const wormholeUseContract = await createContractUser(
|
||||
"wormhole",
|
||||
config.contractAccount,
|
||||
contractMethods
|
||||
);
|
||||
|
||||
console.log("Finish deploy contracts and create test accounts");
|
||||
return { wormholeUseContract };
|
||||
}
|
||||
|
||||
async function test() {
|
||||
|
||||
const guardianKey = "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe";
|
||||
const guardianPrivKeys = "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
|
||||
|
||||
const message = Buffer.from("Hello there").toString("hex");
|
||||
|
||||
// The actual hash function here doesn't matter that much but this happens to be the hash function used by wormhole
|
||||
const hash = web3Utils.keccak256(web3Utils.keccak256("0x" + message)).substr(2);
|
||||
|
||||
|
||||
const ec = new elliptic.ec("secp256k1");
|
||||
const key = ec.keyFromPrivate(guardianPrivKeys);
|
||||
const signature = key.sign(hash, { canonical: true });
|
||||
|
||||
// const key.recoverPubKey(hash, signature, signature.recoveryParam, "hex")
|
||||
|
||||
const sig = signature.r.toString(16) + signature.s.toString(16);
|
||||
console.log("hash: " + hash);
|
||||
console.log("sig: " + sig);
|
||||
console.log("originalKey: " + guardianKey);
|
||||
|
||||
await initNear();
|
||||
const { wormholeUseContract } = await initTest();
|
||||
|
||||
let ret = await wormholeUseContract.recover_key({ args: { digest: hash, sig: sig, recovery: signature.recoveryParam } });
|
||||
|
||||
console.log(ret);
|
||||
console.log(guardianKey);
|
||||
}
|
||||
|
||||
test();
|
|
@ -0,0 +1,874 @@
|
|||
// npx pretty-quick
|
||||
|
||||
const sha256 = require("js-sha256");
|
||||
const fs = require("fs").promises;
|
||||
const assert = require("assert").strict;
|
||||
const fetch = require("node-fetch");
|
||||
const elliptic = require("elliptic");
|
||||
const web3Utils = require("web3-utils");
|
||||
import { zeroPad } from "@ethersproject/bytes";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
|
||||
import {
|
||||
connect as nearConnect,
|
||||
keyStores as nearKeyStores,
|
||||
utils as nearUtils,
|
||||
Account as nearAccount,
|
||||
providers as nearProviders,
|
||||
} from "@certusone/wormhole-sdk/node_modules/near-api-js";
|
||||
|
||||
const BN = require("bn.js");
|
||||
|
||||
import { TestLib } from "./testlib";
|
||||
|
||||
import algosdk, {
|
||||
Account,
|
||||
Algodv2,
|
||||
OnApplicationComplete,
|
||||
SuggestedParams,
|
||||
bigIntToBytes,
|
||||
decodeAddress,
|
||||
getApplicationAddress,
|
||||
makeApplicationCallTxnFromObject,
|
||||
makePaymentTxnWithSuggestedParamsFromObject,
|
||||
waitForConfirmation,
|
||||
} from "@certusone/wormhole-sdk/node_modules/algosdk";
|
||||
|
||||
import {
|
||||
createAsset,
|
||||
getAlgoClient,
|
||||
getBalance,
|
||||
getBalances,
|
||||
getForeignAssetFromVaaAlgorand,
|
||||
getTempAccounts,
|
||||
signSendAndConfirmAlgorand,
|
||||
} from "./algoHelpers";
|
||||
|
||||
import {
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_NEAR,
|
||||
ChainId,
|
||||
ChainName,
|
||||
textToHexString,
|
||||
textToUint8Array,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/utils";
|
||||
|
||||
import { safeBigIntToNumber } from "@certusone/wormhole-sdk/lib/cjs/utils/bigint";
|
||||
|
||||
import {
|
||||
CONTRACTS,
|
||||
attestNearFromNear,
|
||||
attestTokenFromNear,
|
||||
attestFromAlgorand,
|
||||
createWrappedOnAlgorand,
|
||||
createWrappedOnNear,
|
||||
getEmitterAddressAlgorand,
|
||||
getForeignAssetAlgorand,
|
||||
getForeignAssetNear,
|
||||
getIsTransferCompletedNear,
|
||||
getIsWrappedAssetNear,
|
||||
getOriginalAssetNear,
|
||||
getSignedVAAWithRetry,
|
||||
redeemOnAlgorand,
|
||||
redeemOnNear,
|
||||
transferFromAlgorand,
|
||||
transferNearFromNear,
|
||||
transferTokenFromNear,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
const wh = require("@certusone/wormhole-sdk");
|
||||
|
||||
import { parseSequenceFromLogAlgorand } from "@certusone/wormhole-sdk/lib/cjs/bridge";
|
||||
|
||||
import {
|
||||
getMessageFee,
|
||||
optin,
|
||||
TransactionSignerPair,
|
||||
_parseVAAAlgorand,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/algorand";
|
||||
|
||||
export const uint8ArrayToHex = (a: Uint8Array): string =>
|
||||
Buffer.from(a).toString("hex");
|
||||
|
||||
export const hexToUint8Array = (h: string): Uint8Array =>
|
||||
new Uint8Array(Buffer.from(h, "hex"));
|
||||
|
||||
function getConfig(env: any) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
wormholeAccount: "wormhole.test.near",
|
||||
tokenAccount: "token.test.near",
|
||||
userAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
user2Account:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export function logNearGas(result: any, comment: string) {
|
||||
const { totalGasBurned, totalTokensBurned } = result.receipts_outcome.reduce(
|
||||
(acc: any, receipt: any) => {
|
||||
acc.totalGasBurned += receipt.outcome.gas_burnt;
|
||||
acc.totalTokensBurned += nearUtils.format.formatNearAmount(
|
||||
receipt.outcome.tokens_burnt
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
totalGasBurned: result.transaction_outcome.outcome.gas_burnt,
|
||||
totalTokensBurned: nearUtils.format.formatNearAmount(
|
||||
result.transaction_outcome.outcome.tokens_burnt
|
||||
),
|
||||
}
|
||||
);
|
||||
console.log(
|
||||
comment,
|
||||
"totalGasBurned",
|
||||
totalGasBurned,
|
||||
"totalTokensBurned",
|
||||
totalTokensBurned
|
||||
);
|
||||
}
|
||||
|
||||
export function parseSequenceFromLogNear(result: any): [number, string] {
|
||||
let sequence = "";
|
||||
for (const o of result.receipts_outcome) {
|
||||
for (const l of o.outcome.logs) {
|
||||
if (l.startsWith("EVENT_JSON:")) {
|
||||
const body = JSON.parse(l.slice(11));
|
||||
if (body.standard === "wormhole" && body.event === "publish") {
|
||||
return [body.seq, body.emitter];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [-1, ""];
|
||||
}
|
||||
|
||||
async function testNearSDK() {
|
||||
let config = getConfig(process.env.NEAR_ENV || "sandbox");
|
||||
|
||||
// Retrieve the validator key directly in the Tilt environment
|
||||
const response = await fetch("http://localhost:3031/validator_key.json");
|
||||
|
||||
const keyFile = await response.json();
|
||||
|
||||
let masterKey = nearUtils.KeyPair.fromString(
|
||||
keyFile.secret_key || keyFile.private_key
|
||||
);
|
||||
let masterPubKey = masterKey.getPublicKey();
|
||||
|
||||
let keyStore = new nearKeyStores.InMemoryKeyStore();
|
||||
keyStore.setKey(
|
||||
config.networkId as string,
|
||||
config.masterAccount as string,
|
||||
masterKey
|
||||
);
|
||||
|
||||
let near = await nearConnect({
|
||||
headers: {},
|
||||
deps: {
|
||||
keyStore,
|
||||
},
|
||||
networkId: config.networkId as string,
|
||||
nodeUrl: config.nodeUrl as string,
|
||||
});
|
||||
let masterAccount = new nearAccount(
|
||||
near.connection,
|
||||
config.masterAccount as string
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR masterAccount: " +
|
||||
JSON.stringify(await masterAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
let userKey = nearUtils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(
|
||||
config.networkId as string,
|
||||
config.userAccount as string,
|
||||
userKey
|
||||
);
|
||||
let user2Key = nearUtils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(
|
||||
config.networkId as string,
|
||||
config.user2Account as string,
|
||||
user2Key
|
||||
);
|
||||
|
||||
console.log(
|
||||
"creating a user account: " +
|
||||
config.userAccount +
|
||||
" with key " +
|
||||
userKey.getPublicKey()
|
||||
);
|
||||
|
||||
await masterAccount.createAccount(
|
||||
config.userAccount as string,
|
||||
userKey.getPublicKey(),
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
const userAccount = new nearAccount(
|
||||
near.connection,
|
||||
config.userAccount as string
|
||||
);
|
||||
|
||||
console.log(
|
||||
"creating a second user account: " +
|
||||
config.user2Account +
|
||||
" with key " +
|
||||
user2Key.getPublicKey()
|
||||
);
|
||||
|
||||
await masterAccount.createAccount(
|
||||
config.user2Account as string,
|
||||
user2Key.getPublicKey(),
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
const user2Account = new nearAccount(
|
||||
near.connection,
|
||||
config.user2Account as string
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Creating new random non-wormhole token and air dropping some tokens to myself"
|
||||
);
|
||||
|
||||
let randoToken = nearProviders.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: "test.test.near",
|
||||
methodName: "deploy_ft",
|
||||
args: {
|
||||
account: userAccount.accountId,
|
||||
},
|
||||
gas: new BN("300000000000000"),
|
||||
})
|
||||
);
|
||||
|
||||
let token_bridge = CONTRACTS.DEVNET.near.token_bridge;
|
||||
let core_bridge = CONTRACTS.DEVNET.near.core;
|
||||
|
||||
console.log("Setting up algorand wallet");
|
||||
|
||||
let algoCore = BigInt(CONTRACTS.DEVNET.algorand.core);
|
||||
let algoToken = BigInt(CONTRACTS.DEVNET.algorand.token_bridge);
|
||||
|
||||
const tbAddr: string = getApplicationAddress(algoToken);
|
||||
const decTbAddr: Uint8Array = decodeAddress(tbAddr).publicKey;
|
||||
const aa: string = uint8ArrayToHex(decTbAddr);
|
||||
|
||||
const algoClient: algosdk.Algodv2 = getAlgoClient();
|
||||
const tempAccts: Account[] = await getTempAccounts();
|
||||
const numAccts: number = tempAccts.length;
|
||||
|
||||
const algoWallet: Account = tempAccts[0];
|
||||
|
||||
console.log("Creating USDC on Near");
|
||||
|
||||
let ts = new TestLib();
|
||||
let seq = Math.floor(new Date().getTime() / 1000);
|
||||
let usdcvaa = ts.hexStringToUint8Array(
|
||||
ts.genAssetMeta(
|
||||
ts.singleGuardianPrivKey,
|
||||
0,
|
||||
1,
|
||||
seq,
|
||||
"4523c3F29447d1f32AEa95BEBD00383c4640F1b4".toLowerCase(),
|
||||
1,
|
||||
8,
|
||||
"USDC",
|
||||
"CircleCoin"
|
||||
)
|
||||
);
|
||||
|
||||
seq = seq + 1;
|
||||
|
||||
let usdcp = _parseVAAAlgorand(usdcvaa);
|
||||
|
||||
//console.log(usdcp);
|
||||
|
||||
console.log("calling createWrappedOnNear to create usdc");
|
||||
|
||||
if (
|
||||
(await getIsTransferCompletedNear(userAccount, token_bridge, usdcvaa)) ==
|
||||
true
|
||||
) {
|
||||
console.log("getIsTransferCompleted returned incorrect value (true)");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let usdc = await createWrappedOnNear(userAccount, token_bridge, usdcvaa);
|
||||
console.log(usdc);
|
||||
|
||||
if (usdc === "") {
|
||||
console.log("null usdc ... we failed to create it?!");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
(await getIsTransferCompletedNear(userAccount, token_bridge, usdcvaa)) ==
|
||||
false
|
||||
) {
|
||||
console.log("getIsTransferCompleted returned incorrect value (false)");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let aname = await getForeignAssetNear(
|
||||
userAccount,
|
||||
token_bridge,
|
||||
usdcp.FromChain as ChainId,
|
||||
usdcp.Contract as string
|
||||
);
|
||||
if (aname !== usdc) {
|
||||
console.log(aname + " !== " + usdc);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(aname + " === " + usdc);
|
||||
}
|
||||
|
||||
console.log("Creating USDC token on algorand");
|
||||
let tx = await createWrappedOnAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
usdcvaa
|
||||
);
|
||||
await signSendAndConfirmAlgorand(algoClient, tx, algoWallet);
|
||||
|
||||
let account_hash = await userAccount.viewFunction(
|
||||
token_bridge,
|
||||
"hash_account",
|
||||
{
|
||||
account: userAccount.accountId,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(account_hash);
|
||||
|
||||
let myAddress = account_hash[1];
|
||||
|
||||
console.log("Airdropping USDC on myself");
|
||||
{
|
||||
let trans = ts.genTransfer(
|
||||
ts.singleGuardianPrivKey,
|
||||
0,
|
||||
1,
|
||||
seq,
|
||||
10000,
|
||||
"4523c3F29447d1f32AEa95BEBD00383c4640F1b4".toLowerCase(),
|
||||
1,
|
||||
myAddress, // lets send it to the correct user (use the hash)
|
||||
CHAIN_ID_NEAR,
|
||||
0
|
||||
);
|
||||
console.log(trans);
|
||||
|
||||
try {
|
||||
console.log(
|
||||
await redeemOnNear(userAccount, token_bridge, hexToUint8Array(trans))
|
||||
);
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch (error) {
|
||||
console.log("Exception thrown.. nice.. we dont suck");
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
console.log("Registering the receiving account");
|
||||
|
||||
let myAddress2 = nearProviders.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: token_bridge,
|
||||
methodName: "register_account",
|
||||
args: { account: userAccount.accountId },
|
||||
gas: new BN("100000000000000"),
|
||||
attachedDeposit: new BN("2000000000000000000000"), // 0.002 NEAR
|
||||
})
|
||||
);
|
||||
console.log("myAddress: " + myAddress2);
|
||||
|
||||
console.log(
|
||||
await redeemOnNear(userAccount, token_bridge, hexToUint8Array(trans))
|
||||
);
|
||||
}
|
||||
console.log(".. created some USDC");
|
||||
|
||||
let nativeAttest;
|
||||
{
|
||||
console.log("attesting: " + randoToken);
|
||||
let s = await attestTokenFromNear(
|
||||
userAccount,
|
||||
core_bridge,
|
||||
token_bridge,
|
||||
randoToken
|
||||
);
|
||||
console.log(s);
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
s[1],
|
||||
s[0].toString(),
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
let p = _parseVAAAlgorand(signedVAA);
|
||||
|
||||
console.log(p.FromChain as ChainId, p.Contract as string);
|
||||
|
||||
let a = await getForeignAssetNear(
|
||||
userAccount,
|
||||
token_bridge,
|
||||
p.FromChain as ChainId,
|
||||
p.Contract as string
|
||||
);
|
||||
if (a !== randoToken) {
|
||||
console.log(a + " !== " + randoToken);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
nativeAttest = signedVAA;
|
||||
}
|
||||
|
||||
let nearAttest;
|
||||
{
|
||||
console.log("attesting Near itself");
|
||||
let s = await attestNearFromNear(userAccount, core_bridge, token_bridge);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
s[1],
|
||||
s[0].toString(),
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
let p = _parseVAAAlgorand(signedVAA);
|
||||
let a = await getForeignAssetNear(
|
||||
userAccount,
|
||||
token_bridge,
|
||||
p.FromChain as ChainId,
|
||||
p.Contract as string
|
||||
);
|
||||
console.log(
|
||||
"chain: {} contract: {} account: {}",
|
||||
p.FromChain,
|
||||
p.Contract,
|
||||
a
|
||||
);
|
||||
|
||||
nearAttest = signedVAA;
|
||||
}
|
||||
|
||||
console.log("Creating a native token from near onto algorand");
|
||||
tx = await createWrappedOnAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
nativeAttest
|
||||
);
|
||||
await signSendAndConfirmAlgorand(algoClient, tx, algoWallet);
|
||||
console.log("Creating NEAR from near onto algorand");
|
||||
tx = await createWrappedOnAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
nearAttest
|
||||
);
|
||||
await signSendAndConfirmAlgorand(algoClient, tx, algoWallet);
|
||||
|
||||
console.log("Shock and awe...");
|
||||
|
||||
if (usdc === "") {
|
||||
console.log("null usdc");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let wrappedTransfer;
|
||||
{
|
||||
console.log(
|
||||
"transfer wrapped token from near to algorand",
|
||||
userAccount,
|
||||
core_bridge,
|
||||
token_bridge,
|
||||
usdc
|
||||
);
|
||||
|
||||
let s = await transferTokenFromNear(
|
||||
userAccount,
|
||||
core_bridge,
|
||||
token_bridge,
|
||||
usdc,
|
||||
BigInt(100),
|
||||
decodeAddress(algoWallet.addr).publicKey,
|
||||
8,
|
||||
BigInt(0)
|
||||
);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
s[1],
|
||||
s[0].toString(),
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
wrappedTransfer = signedVAA;
|
||||
}
|
||||
|
||||
let randoTransfer;
|
||||
{
|
||||
console.log("transfer rando token from near to algorand");
|
||||
let s = await transferTokenFromNear(
|
||||
userAccount,
|
||||
core_bridge,
|
||||
token_bridge,
|
||||
randoToken,
|
||||
BigInt(10000000),
|
||||
decodeAddress(algoWallet.addr).publicKey,
|
||||
8,
|
||||
BigInt(0)
|
||||
);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
s[1],
|
||||
s[0].toString(),
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
randoTransfer = signedVAA;
|
||||
}
|
||||
|
||||
let nearTransfer;
|
||||
{
|
||||
console.log("transfer near from near to algorand");
|
||||
let s = await transferNearFromNear(
|
||||
userAccount,
|
||||
core_bridge,
|
||||
token_bridge,
|
||||
BigInt(1000000000000000000000000),
|
||||
decodeAddress(algoWallet.addr).publicKey,
|
||||
8,
|
||||
BigInt(0)
|
||||
);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
s[1],
|
||||
s[0].toString(),
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
nearTransfer = signedVAA;
|
||||
}
|
||||
|
||||
let usdcAssetId;
|
||||
{
|
||||
console.log("redeeming our wrapped USDC from Near on Algorand");
|
||||
const tx = await redeemOnAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
wrappedTransfer,
|
||||
algoWallet.addr
|
||||
);
|
||||
await signSendAndConfirmAlgorand(algoClient, tx, algoWallet);
|
||||
|
||||
let p = _parseVAAAlgorand(wrappedTransfer);
|
||||
usdcAssetId = (await getForeignAssetAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
p.FromChain as ChainId,
|
||||
p.Contract as string
|
||||
)) as bigint;
|
||||
console.log("usdc asset id: " + usdcAssetId);
|
||||
}
|
||||
|
||||
let randoAssetId;
|
||||
{
|
||||
console.log("redeeming our near native asset on Algorand");
|
||||
const tx = await redeemOnAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
randoTransfer,
|
||||
algoWallet.addr
|
||||
);
|
||||
await signSendAndConfirmAlgorand(algoClient, tx, algoWallet);
|
||||
|
||||
let p = _parseVAAAlgorand(randoTransfer);
|
||||
randoAssetId = (await getForeignAssetAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
p.FromChain as ChainId,
|
||||
p.Contract as string
|
||||
)) as bigint;
|
||||
console.log("randoToken asset id: " + randoAssetId);
|
||||
}
|
||||
|
||||
let nearAssetId;
|
||||
{
|
||||
console.log("redeeming NEAR on Algorand");
|
||||
const tx = await redeemOnAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
nearTransfer,
|
||||
algoWallet.addr
|
||||
);
|
||||
await signSendAndConfirmAlgorand(algoClient, tx, algoWallet);
|
||||
|
||||
let p = _parseVAAAlgorand(nearTransfer);
|
||||
nearAssetId = (await getForeignAssetAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
p.FromChain as ChainId,
|
||||
p.Contract as string
|
||||
)) as bigint;
|
||||
console.log("NEAR asset id: " + nearAssetId);
|
||||
}
|
||||
|
||||
const emitterAddr = getEmitterAddressAlgorand(algoToken);
|
||||
|
||||
console.log("wallet addr: " + algoWallet.addr);
|
||||
console.log("usdcAssetId: " + usdcAssetId);
|
||||
|
||||
console.log("transfering USDC from Algo To Near... getting the vaa");
|
||||
console.log("myAddress: " + myAddress);
|
||||
|
||||
let transferAlgoToNearUSDC;
|
||||
{
|
||||
const AmountToTransfer: number = 100;
|
||||
const Fee: number = 20;
|
||||
const transferTxs = await transferFromAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
usdcAssetId,
|
||||
BigInt(AmountToTransfer),
|
||||
myAddress,
|
||||
CHAIN_ID_NEAR,
|
||||
BigInt(Fee)
|
||||
);
|
||||
const transferResult = await signSendAndConfirmAlgorand(
|
||||
algoClient,
|
||||
transferTxs,
|
||||
algoWallet
|
||||
);
|
||||
const txSid = parseSequenceFromLogAlgorand(transferResult);
|
||||
transferAlgoToNearUSDC = (
|
||||
await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_ALGORAND,
|
||||
emitterAddr,
|
||||
txSid,
|
||||
{ transport: NodeHttpTransport() }
|
||||
)
|
||||
).vaaBytes;
|
||||
}
|
||||
|
||||
console.log("transfering rando from Algo To Near... getting the vaa");
|
||||
let transferAlgoToNearRando;
|
||||
{
|
||||
const AmountToTransfer: number = 100;
|
||||
const Fee: number = 20;
|
||||
const transferTxs = await transferFromAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
randoAssetId,
|
||||
BigInt(AmountToTransfer),
|
||||
myAddress,
|
||||
CHAIN_ID_NEAR,
|
||||
BigInt(Fee)
|
||||
);
|
||||
const transferResult = await signSendAndConfirmAlgorand(
|
||||
algoClient,
|
||||
transferTxs,
|
||||
algoWallet
|
||||
);
|
||||
const txSid = parseSequenceFromLogAlgorand(transferResult);
|
||||
transferAlgoToNearRando = (
|
||||
await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_ALGORAND,
|
||||
emitterAddr,
|
||||
txSid,
|
||||
{ transport: NodeHttpTransport() }
|
||||
)
|
||||
).vaaBytes;
|
||||
}
|
||||
|
||||
console.log("transfering NEAR from Algo To Near... getting the vaa");
|
||||
let transferAlgoToNearNEAR;
|
||||
{
|
||||
const AmountToTransfer: number = 100;
|
||||
const Fee: number = 20;
|
||||
const transferTxs = await transferFromAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
nearAssetId,
|
||||
BigInt(AmountToTransfer),
|
||||
myAddress,
|
||||
CHAIN_ID_NEAR,
|
||||
BigInt(Fee)
|
||||
);
|
||||
const transferResult = await signSendAndConfirmAlgorand(
|
||||
algoClient,
|
||||
transferTxs,
|
||||
algoWallet
|
||||
);
|
||||
const txSid = parseSequenceFromLogAlgorand(transferResult);
|
||||
transferAlgoToNearNEAR = (
|
||||
await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_ALGORAND,
|
||||
emitterAddr,
|
||||
txSid,
|
||||
{ transport: NodeHttpTransport() }
|
||||
)
|
||||
).vaaBytes;
|
||||
}
|
||||
|
||||
console.log("redeeming USDC on Near");
|
||||
console.log(
|
||||
await redeemOnNear(user2Account, token_bridge, transferAlgoToNearUSDC)
|
||||
);
|
||||
|
||||
console.log(
|
||||
"redeeming Rando on Near: " + uint8ArrayToHex(transferAlgoToNearRando)
|
||||
);
|
||||
console.log(
|
||||
await redeemOnNear(user2Account, token_bridge, transferAlgoToNearRando)
|
||||
);
|
||||
|
||||
console.log("redeeming NEAR on Near");
|
||||
console.log(
|
||||
await redeemOnNear(user2Account, token_bridge, transferAlgoToNearNEAR)
|
||||
);
|
||||
|
||||
let userAccount2Address = nearProviders.getTransactionLastResult(
|
||||
await userAccount.functionCall({
|
||||
contractId: token_bridge,
|
||||
methodName: "register_account",
|
||||
args: { account: user2Account.accountId },
|
||||
gas: new BN("100000000000000"),
|
||||
attachedDeposit: new BN("2000000000000000000000"), // 0.002 NEAR
|
||||
})
|
||||
);
|
||||
console.log("userAccount2Address: " + userAccount2Address);
|
||||
|
||||
let transferAlgoToNearP3;
|
||||
{
|
||||
const AmountToTransfer: number = 100;
|
||||
const Fee: number = 20;
|
||||
const transferTxs = await transferFromAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
nearAssetId,
|
||||
BigInt(AmountToTransfer),
|
||||
userAccount2Address,
|
||||
CHAIN_ID_NEAR,
|
||||
BigInt(Fee),
|
||||
hexToUint8Array("ff")
|
||||
);
|
||||
const transferResult = await signSendAndConfirmAlgorand(
|
||||
algoClient,
|
||||
transferTxs,
|
||||
algoWallet
|
||||
);
|
||||
const txSid = parseSequenceFromLogAlgorand(transferResult);
|
||||
transferAlgoToNearP3 = (
|
||||
await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_ALGORAND,
|
||||
emitterAddr,
|
||||
txSid,
|
||||
{ transport: NodeHttpTransport() }
|
||||
)
|
||||
).vaaBytes;
|
||||
}
|
||||
|
||||
{
|
||||
console.log("redeeming P3 NEAR on Near");
|
||||
console.log(
|
||||
await redeemOnNear(user2Account, token_bridge, transferAlgoToNearP3)
|
||||
);
|
||||
|
||||
console.log("transfering rando from Algo To Near... getting the vaa");
|
||||
let transferAlgoToNearRandoP3;
|
||||
{
|
||||
const AmountToTransfer: number = 100;
|
||||
const Fee: number = 20;
|
||||
const transferTxs = await transferFromAlgorand(
|
||||
algoClient,
|
||||
algoToken,
|
||||
algoCore,
|
||||
algoWallet.addr,
|
||||
randoAssetId,
|
||||
BigInt(AmountToTransfer),
|
||||
userAccount2Address,
|
||||
CHAIN_ID_NEAR,
|
||||
BigInt(Fee),
|
||||
hexToUint8Array("ff")
|
||||
);
|
||||
const transferResult = await signSendAndConfirmAlgorand(
|
||||
algoClient,
|
||||
transferTxs,
|
||||
algoWallet
|
||||
);
|
||||
const txSid = parseSequenceFromLogAlgorand(transferResult);
|
||||
transferAlgoToNearRandoP3 = (
|
||||
await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_ALGORAND,
|
||||
emitterAddr,
|
||||
txSid,
|
||||
{ transport: NodeHttpTransport() }
|
||||
)
|
||||
).vaaBytes;
|
||||
}
|
||||
|
||||
console.log("redeeming P3 random on Near");
|
||||
console.log(
|
||||
await redeemOnNear(user2Account, token_bridge, transferAlgoToNearRandoP3)
|
||||
);
|
||||
}
|
||||
|
||||
console.log("What next?");
|
||||
}
|
||||
|
||||
testNearSDK();
|
|
@ -0,0 +1,900 @@
|
|||
// npx pretty-quick
|
||||
|
||||
const sha256 = require("js-sha256");
|
||||
const nearAPI = require("near-api-js");
|
||||
const BN = require("bn.js");
|
||||
const fs = require("fs").promises;
|
||||
const assert = require("assert").strict;
|
||||
const fetch = require("node-fetch");
|
||||
const elliptic = require("elliptic");
|
||||
const web3Utils = require("web3-utils");
|
||||
import { zeroPad } from "@ethersproject/bytes";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
|
||||
import { TestLib } from "./testlib";
|
||||
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_NEAR,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/utils";
|
||||
|
||||
import { _parseVAAAlgorand } from "@certusone/wormhole-sdk/lib/cjs/algorand";
|
||||
|
||||
import { getSignedVAAWithRetry } from "@certusone/wormhole-sdk";
|
||||
|
||||
function getConfig(env: any) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
wormholeAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "wormhole.test.near",
|
||||
tokenAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "token.test.near",
|
||||
testAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "test.test.near",
|
||||
userAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const wormholeMethods = {
|
||||
viewMethods: [],
|
||||
changeMethods: ["boot_wormhole", "submit_vaa"],
|
||||
};
|
||||
|
||||
const tokenMethods = {
|
||||
viewMethods: [],
|
||||
changeMethods: [
|
||||
"boot_portal",
|
||||
"submit_vaa",
|
||||
"submit_vaa_callback",
|
||||
"attest_near",
|
||||
"attest_token",
|
||||
"send_transfer_near",
|
||||
"send_transfer_wormhole_token",
|
||||
"account_hash",
|
||||
],
|
||||
};
|
||||
|
||||
const testMethods = {
|
||||
viewMethods: [],
|
||||
changeMethods: ["deploy_ft"],
|
||||
};
|
||||
|
||||
const ftMethods = {
|
||||
viewMethods: [],
|
||||
changeMethods: ["ft_transfer_call", "storage_deposit"],
|
||||
};
|
||||
|
||||
let config: any;
|
||||
let masterAccount: any;
|
||||
let _tokenAccount: any;
|
||||
let _wormholeAccount: any;
|
||||
let _testAccount: any;
|
||||
let masterKey: any;
|
||||
let masterPubKey: any;
|
||||
let keyStore: any;
|
||||
let near: any;
|
||||
|
||||
let userAccount: any;
|
||||
let userKey: any;
|
||||
let userPubKey: any;
|
||||
|
||||
async function initNear() {
|
||||
config = getConfig(process.env.NEAR_ENV || "sandbox");
|
||||
|
||||
// Retrieve the validator key directly in the Tilt environment
|
||||
const response = await fetch("http://localhost:3031/validator_key.json");
|
||||
|
||||
const keyFile = await response.json();
|
||||
|
||||
console.log(keyFile);
|
||||
|
||||
masterKey = nearAPI.utils.KeyPair.fromString(
|
||||
keyFile.secret_key || keyFile.private_key
|
||||
);
|
||||
masterPubKey = masterKey.getPublicKey();
|
||||
|
||||
userKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
console.log(userKey);
|
||||
|
||||
keyStore = new nearAPI.keyStores.InMemoryKeyStore();
|
||||
|
||||
keyStore.setKey(config.networkId, config.masterAccount, masterKey);
|
||||
keyStore.setKey(config.networkId, config.userAccount, userKey);
|
||||
|
||||
near = await nearAPI.connect({
|
||||
deps: {
|
||||
keyStore,
|
||||
},
|
||||
networkId: config.networkId,
|
||||
nodeUrl: config.nodeUrl,
|
||||
});
|
||||
masterAccount = new nearAPI.Account(near.connection, config.masterAccount);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR masterAccount: " +
|
||||
JSON.stringify(await masterAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
let resp = await masterAccount.createAccount(
|
||||
config.userAccount,
|
||||
userKey.getPublicKey(),
|
||||
new BN(10).pow(new BN(25))
|
||||
);
|
||||
|
||||
console.log(resp);
|
||||
|
||||
userAccount = new nearAPI.Account(near.connection, config.userAccount);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR userAccount: " +
|
||||
JSON.stringify(await userAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
// console.log(await userAccount.sendMoney(config.masterAccount, nearAPI.utils.format.parseNearAmount("1.5")));;
|
||||
// console.log("Sent some money: " + JSON.stringify(await userAccount.getAccountBalance()));
|
||||
}
|
||||
|
||||
async function createContractUser(
|
||||
accountPrefix: any,
|
||||
contractAccountId: any,
|
||||
methods: any
|
||||
) {
|
||||
let accountId =
|
||||
Math.floor(Math.random() * 10000).toString() +
|
||||
accountPrefix +
|
||||
"." +
|
||||
config.masterAccount;
|
||||
|
||||
console.log(accountId);
|
||||
|
||||
let randomKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
|
||||
let resp = await masterAccount.createAccount(
|
||||
accountId,
|
||||
randomKey.getPublicKey(),
|
||||
new BN(10).pow(new BN(28))
|
||||
);
|
||||
console.log("accountId: " + JSON.stringify(resp));
|
||||
|
||||
keyStore.setKey(config.networkId, accountId, randomKey);
|
||||
const account = new nearAPI.Account(near.connection, accountId);
|
||||
const accountUseContract = new nearAPI.Contract(
|
||||
account,
|
||||
contractAccountId,
|
||||
methods
|
||||
);
|
||||
return accountUseContract;
|
||||
}
|
||||
|
||||
async function initTest() {
|
||||
const wormholeContract = await fs.readFile(
|
||||
"./contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm"
|
||||
);
|
||||
const tokenContract = await fs.readFile(
|
||||
"./contracts/portal/target/wasm32-unknown-unknown/release/near_token_bridge.wasm"
|
||||
);
|
||||
const testContract = await fs.readFile(
|
||||
"./contracts/mock-bridge-integration/target/wasm32-unknown-unknown/release/near_mock_bridge_integration.wasm"
|
||||
);
|
||||
|
||||
let randomKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(config.networkId, config.wormholeAccount, randomKey);
|
||||
|
||||
_wormholeAccount = await masterAccount.createAndDeployContract(
|
||||
config.wormholeAccount,
|
||||
randomKey.getPublicKey(),
|
||||
wormholeContract,
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
|
||||
randomKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(config.networkId, config.tokenAccount, randomKey);
|
||||
|
||||
_tokenAccount = await masterAccount.createAndDeployContract(
|
||||
config.tokenAccount,
|
||||
randomKey.getPublicKey(),
|
||||
tokenContract,
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
|
||||
console.log("tokenAccount: " + config.tokenAccount);
|
||||
|
||||
_testAccount = await masterAccount.createAndDeployContract(
|
||||
config.testAccount,
|
||||
randomKey.getPublicKey(),
|
||||
testContract,
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
|
||||
const wormholeUseContract = await createContractUser(
|
||||
"wormhole_user",
|
||||
config.wormholeAccount,
|
||||
wormholeMethods
|
||||
);
|
||||
|
||||
const tokenUseContract = await createContractUser(
|
||||
"tokenbridge_user",
|
||||
config.tokenAccount,
|
||||
tokenMethods
|
||||
);
|
||||
|
||||
const testUseContract = await createContractUser(
|
||||
"test_user",
|
||||
config.testAccount,
|
||||
testMethods
|
||||
);
|
||||
|
||||
//
|
||||
// console.log(userUseContract.account.accountId);
|
||||
|
||||
console.log("Finish deploy contracts and create test accounts");
|
||||
return {
|
||||
wormholeUseContract,
|
||||
tokenUseContract,
|
||||
testUseContract,
|
||||
};
|
||||
}
|
||||
|
||||
function delay(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function nearParseResultForLogs(result: any): [number, string] {
|
||||
for (const o of result.receipts_outcome) {
|
||||
for (const l of o.outcome.logs) {
|
||||
console.log(l);
|
||||
if (l.startsWith("EVENT_JSON:")) {
|
||||
const body = JSON.parse(l.slice(11));
|
||||
if (body.standard == "wormhole" && body.event == "publish") {
|
||||
console.log(body);
|
||||
return [body.seq, body.emitter];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [-1, ""];
|
||||
}
|
||||
|
||||
async function test() {
|
||||
let fastTest = true;
|
||||
let ts = new TestLib();
|
||||
|
||||
await initNear();
|
||||
const { wormholeUseContract, tokenUseContract, testUseContract } =
|
||||
await initTest();
|
||||
|
||||
console.log("Booting guardian set with index 0");
|
||||
await wormholeUseContract.boot_wormhole({
|
||||
args: { gset: 0, addresses: ts.guardianKeys },
|
||||
});
|
||||
console.log("Completed without an error... odd.. I am not sucking yet");
|
||||
|
||||
console.log("Booting up the token bridge");
|
||||
await tokenUseContract.boot_portal({
|
||||
args: { core: config.wormholeAccount },
|
||||
});
|
||||
console.log("token bridge booted");
|
||||
|
||||
let seq = 1;
|
||||
|
||||
console.log("lets upgrade the governance set to 1");
|
||||
let vaa = ts.genGuardianSetUpgrade(
|
||||
ts.guardianPrivKeys,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
seq,
|
||||
ts.guardianKeys
|
||||
);
|
||||
|
||||
console.log("sending it to the core contract");
|
||||
await wormholeUseContract.submit_vaa({ args: { vaa: vaa } });
|
||||
|
||||
seq = seq + 1;
|
||||
|
||||
if (!fastTest) {
|
||||
console.log("Its parsed... lets do it again!!");
|
||||
try {
|
||||
await wormholeUseContract.submit_vaa({ args: { vaa: vaa } });
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. nice.. we dont suck");
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Lets try to send a governence message (SetFee) with the wrong index"
|
||||
);
|
||||
vaa = ts.genGSetFee(ts.guardianPrivKeys, 0, 1, seq, CHAIN_ID_NEAR, 5);
|
||||
try {
|
||||
await wormholeUseContract.submit_vaa({ args: { vaa: vaa } });
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log(
|
||||
"Exception thrown.. nice.. this was with the wrong governance set"
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Lets try to send a governence message (SetFee) with the correct index but the wrong chain"
|
||||
);
|
||||
vaa = ts.genGSetFee(ts.guardianPrivKeys, 1, 1, seq, CHAIN_ID_ALGORAND, 5);
|
||||
try {
|
||||
await wormholeUseContract.submit_vaa({ args: { vaa: vaa } });
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. that is correct... ");
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Lets try to send a governence message (SetFee) with the correct index but for all chains"
|
||||
);
|
||||
vaa = ts.genGSetFee(ts.guardianPrivKeys, 1, 1, seq, 0, 5);
|
||||
try {
|
||||
await wormholeUseContract.submit_vaa({ args: { vaa: vaa } });
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. that is correct... ");
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Lets try to send a governence message (SetFee) with the correct index and the correct chain"
|
||||
);
|
||||
|
||||
vaa = ts.genGSetFee(ts.guardianPrivKeys, 1, 1, seq, CHAIN_ID_NEAR, 5);
|
||||
await wormholeUseContract.submit_vaa({ args: { vaa: vaa } });
|
||||
console.log("boo yaah! this was supposed to pass and it did");
|
||||
|
||||
seq = seq + 1;
|
||||
|
||||
console.log("lets try to call the vaa_vallback directly");
|
||||
try {
|
||||
await tokenUseContract.submit_vaa_callback({ args: {} });
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. that is correct... ");
|
||||
}
|
||||
|
||||
try {
|
||||
vaa = ts.genRegisterChain(ts.guardianPrivKeys, 0, 1, seq, 1);
|
||||
console.log(
|
||||
"Now lets call submit_vaa with a valid vaa (register the solana chain) on the token bridge.. with the wrong governance set"
|
||||
);
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
});
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. that is correct... ");
|
||||
}
|
||||
}
|
||||
|
||||
vaa = ts.genRegisterChain(ts.guardianPrivKeys, 1, 1, seq, 1);
|
||||
|
||||
let is_completed = await tokenUseContract.account.viewFunction(config.tokenAccount, "is_transfer_completed", { vaa: vaa });
|
||||
console.log("is_transfer_completed: %s", is_completed);
|
||||
|
||||
console.log(
|
||||
"Now lets call submit_vaa with a valid vaa (register the solana chain) on the token bridge.. with the correct governance set"
|
||||
);
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
});
|
||||
|
||||
seq = seq + 1;
|
||||
|
||||
is_completed = await tokenUseContract.account.viewFunction(config.tokenAccount, "is_transfer_completed", { vaa: vaa });
|
||||
console.log("is_transfer_completed: %s", is_completed);
|
||||
|
||||
if (!fastTest) {
|
||||
try {
|
||||
vaa = ts.genRegisterChain(ts.guardianPrivKeys, 1, 1, seq, 1);
|
||||
console.log(
|
||||
"Now lets call submit_vaa with a valid vaa (register the solana chain) again.. again... this should fail"
|
||||
);
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
});
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. that is correct... ");
|
||||
}
|
||||
}
|
||||
|
||||
let usdcEmitter = "4523c3F29447d1f32AEa95BEBD00383c4640F1b4".toLowerCase();
|
||||
|
||||
if (!fastTest) {
|
||||
try {
|
||||
vaa =
|
||||
ts.genAssetMeta(
|
||||
ts.guardianPrivKeys,
|
||||
1,
|
||||
1,
|
||||
seq,
|
||||
usdcEmitter,
|
||||
1,
|
||||
8,
|
||||
"USDC",
|
||||
"CircleCoin"
|
||||
) + "00";
|
||||
console.log(
|
||||
"Now the fun stuff... lets create some USDC... but pass a hacked vaa"
|
||||
);
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
});
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. that is correct... ");
|
||||
}
|
||||
}
|
||||
vaa = ts.genAssetMeta(
|
||||
ts.guardianPrivKeys,
|
||||
1,
|
||||
1,
|
||||
seq,
|
||||
usdcEmitter,
|
||||
1,
|
||||
8,
|
||||
"USDC2",
|
||||
"CircleCoin2"
|
||||
);
|
||||
console.log("Now the fun stuff... lets create some USDC");
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
});
|
||||
console.log("Try again (since this is an attest)");
|
||||
|
||||
let tname = await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
amount: "33000000000000000000000000",
|
||||
});
|
||||
|
||||
console.log("tname: " + tname);
|
||||
|
||||
console.log("get_original_asset: " + await tokenUseContract.account.viewFunction(config.tokenAccount, "get_original_asset", { token: tname }));
|
||||
|
||||
seq = seq + 1;
|
||||
|
||||
if (!fastTest) {
|
||||
console.log("Lets attest the same thing again");
|
||||
vaa = ts.genAssetMeta(
|
||||
ts.guardianPrivKeys,
|
||||
1,
|
||||
1,
|
||||
seq,
|
||||
usdcEmitter,
|
||||
1,
|
||||
8,
|
||||
"USDC",
|
||||
"CircleCoin"
|
||||
);
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
});
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
amount: "20000000000000000000000000",
|
||||
});
|
||||
|
||||
seq = seq + 1;
|
||||
|
||||
try {
|
||||
console.log("Lets make it fail");
|
||||
vaa = ts.genAssetMeta(
|
||||
ts.guardianPrivKeys,
|
||||
1,
|
||||
1,
|
||||
seq,
|
||||
usdcEmitter,
|
||||
1,
|
||||
8,
|
||||
"USDC20",
|
||||
"OnceUponATimeFarAway"
|
||||
);
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
});
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
});
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. that is correct... ");
|
||||
}
|
||||
|
||||
seq = seq + 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Now, for something useful? send some USDC to our test account: " +
|
||||
tokenUseContract.account.accountId
|
||||
);
|
||||
|
||||
vaa = ts.genTransfer(
|
||||
ts.guardianPrivKeys,
|
||||
1,
|
||||
1,
|
||||
seq,
|
||||
100000,
|
||||
usdcEmitter,
|
||||
1,
|
||||
Buffer.from(tokenUseContract.account.accountId).toString("hex"),
|
||||
CHAIN_ID_NEAR,
|
||||
0
|
||||
);
|
||||
console.log(vaa);
|
||||
// console.log(_parseVAAAlgorand(ts.hexStringToUint8Array(vaa)));
|
||||
await tokenUseContract.submit_vaa({
|
||||
args: { vaa: vaa },
|
||||
gas: 300000000000000,
|
||||
});
|
||||
console.log("well? did it work?!");
|
||||
|
||||
console.log("npm i -g near-cli");
|
||||
console.log(
|
||||
"near --nodeUrl http://localhost:3030 view " +
|
||||
tname +
|
||||
' ft_balance_of \'{"account_id": "' +
|
||||
tokenUseContract.account.accountId +
|
||||
"\"}'"
|
||||
);
|
||||
|
||||
seq = seq + 1;
|
||||
|
||||
if (!fastTest) {
|
||||
try {
|
||||
console.log(
|
||||
"attesting near.. but not attaching any cash to cover this as the first time this emitter is being seen"
|
||||
);
|
||||
let sequence = await tokenUseContract.attest_near({
|
||||
args: {},
|
||||
gas: 100000000000000,
|
||||
});
|
||||
console.log(sequence);
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. that is correct... ");
|
||||
}
|
||||
}
|
||||
let ah = await tokenUseContract.account_hash({ args: {} });
|
||||
console.log(ah);
|
||||
let emitter = sha256.sha256.hex(ah[0]);
|
||||
if (ah[0] != config.tokenAccount) {
|
||||
console.log("The token account does not match what I think it should be");
|
||||
process.exit(1);
|
||||
}
|
||||
if (ah[1] != emitter) {
|
||||
console.log(
|
||||
"The sha256 hash of the token account does not match what I think it should be"
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("emitter: " + emitter);
|
||||
|
||||
if (!fastTest) {
|
||||
{
|
||||
console.log("attesting near but paying for it ");
|
||||
let sequence = await tokenUseContract.attest_near({
|
||||
args: {},
|
||||
gas: 100000000000000,
|
||||
amount: "790000000000000000000",
|
||||
});
|
||||
console.log(sequence);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
emitter,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
}
|
||||
|
||||
{
|
||||
console.log("attesting wormhole token: " + tname);
|
||||
let sequence = await tokenUseContract.attest_token({
|
||||
args: { token: tname },
|
||||
gas: 100000000000000,
|
||||
});
|
||||
console.log(sequence);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
emitter,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
}
|
||||
}
|
||||
|
||||
console.log("deploying a random token and sending some money to a user");
|
||||
|
||||
let randoToken = await testUseContract.deploy_ft({
|
||||
args: { account: tokenUseContract.account.accountId },
|
||||
amount: "7900000000000001234",
|
||||
gas: 300000000000000,
|
||||
});
|
||||
|
||||
console.log(
|
||||
"near --nodeUrl http://localhost:3030 view " +
|
||||
randoToken +
|
||||
' ft_balance_of \'{"account_id": "' +
|
||||
tokenUseContract.account.accountId +
|
||||
"\"}'"
|
||||
);
|
||||
|
||||
if (!fastTest) {
|
||||
{
|
||||
console.log("attesting non wormhole token: " + randoToken);
|
||||
let sequence = await tokenUseContract.attest_token({
|
||||
args: { token: randoToken },
|
||||
gas: 100000000000000,
|
||||
});
|
||||
console.log(sequence);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
emitter,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
console.log(_parseVAAAlgorand(signedVAA));
|
||||
}
|
||||
|
||||
{
|
||||
console.log("sending some NEAR off to a HODLr in the cloud");
|
||||
let sequence = await tokenUseContract.send_transfer_near({
|
||||
args: { receiver: "0011223344", chain: 1, fee: 0, payload: "" },
|
||||
amount: "7900000000000001234",
|
||||
gas: 100000000000000,
|
||||
});
|
||||
console.log(sequence);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
emitter,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
console.log(_parseVAAAlgorand(signedVAA));
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("sending some RANDO off to a HODLr in the cloud");
|
||||
await tokenUseContract.send_transfer_wormhole_token({
|
||||
args: {
|
||||
token: randoToken,
|
||||
receiver: "0011223344",
|
||||
chain: 1,
|
||||
fee: 0,
|
||||
payload: "",
|
||||
amount: 12345,
|
||||
},
|
||||
amount: "7900000000000001234",
|
||||
gas: 100000000000000,
|
||||
});
|
||||
console.log("This should have thrown a exception..");
|
||||
process.exit(1);
|
||||
} catch {
|
||||
console.log("Exception thrown.. nice.. we dont suck");
|
||||
}
|
||||
|
||||
{
|
||||
console.log("sending some USDC off to a HODLr in the cloud: " + tname);
|
||||
console.log(ah);
|
||||
let sequence = await tokenUseContract.send_transfer_wormhole_token({
|
||||
args: {
|
||||
amount: 12345,
|
||||
token: tname,
|
||||
receiver: "0011223344",
|
||||
chain: 1,
|
||||
fee: 0,
|
||||
payload: "",
|
||||
},
|
||||
amount: "800000000000000000000",
|
||||
gas: 100000000000000,
|
||||
});
|
||||
console.log(sequence);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
emitter,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
console.log(_parseVAAAlgorand(signedVAA));
|
||||
}
|
||||
}
|
||||
|
||||
const tokenContract = new nearAPI.Contract(
|
||||
tokenUseContract.account,
|
||||
randoToken,
|
||||
ftMethods
|
||||
);
|
||||
|
||||
console.log(
|
||||
"tokenUseContract.account.accountId",
|
||||
tokenUseContract.account.accountId
|
||||
);
|
||||
console.log(
|
||||
"tokenContract.account.accountId",
|
||||
tokenContract.account.accountId
|
||||
);
|
||||
console.log(
|
||||
"testUseContract.account.accountId",
|
||||
testUseContract.account.accountId
|
||||
);
|
||||
console.log("tokenUseContract.contractId", tokenUseContract.contractId);
|
||||
|
||||
console.log("config.tokenAccount", config.tokenAccount);
|
||||
|
||||
console.log("paying for storage for the destination account");
|
||||
|
||||
console.log(
|
||||
await tokenContract.storage_deposit({
|
||||
args: {
|
||||
account_id: config.tokenAccount,
|
||||
registation_only: true,
|
||||
},
|
||||
amount: "12500000000000000000000",
|
||||
gas: 100000000000000,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
{
|
||||
let result = await tokenUseContract.account.functionCall({
|
||||
contractId: randoToken,
|
||||
methodName: "ft_transfer_call",
|
||||
args: {
|
||||
receiver_id: config.tokenAccount,
|
||||
amount: "3210000000",
|
||||
msg: JSON.stringify(
|
||||
{
|
||||
receiver: "33445566",
|
||||
chain: 1,
|
||||
fee: 0,
|
||||
payload: "",
|
||||
}
|
||||
)
|
||||
},
|
||||
attachedDeposit: "1",
|
||||
gas: 100000000000000,
|
||||
});
|
||||
|
||||
let s = nearParseResultForLogs(result);
|
||||
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
["http://localhost:7071"],
|
||||
CHAIN_ID_NEAR,
|
||||
s[1],
|
||||
s[0].toString(),
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
|
||||
console.log(_parseVAAAlgorand(signedVAA));
|
||||
}
|
||||
|
||||
// console.log(result);
|
||||
// const flatLogs = [
|
||||
// result.transaction_outcome,
|
||||
// ...result.receipts_outcome,
|
||||
// ].reduce((acc, it) => {
|
||||
// if (
|
||||
// it.outcome.logs.length ||
|
||||
// (typeof it.outcome.status === "object" &&
|
||||
// typeof it.outcome.status.Failure === "object")
|
||||
// ) {
|
||||
// return acc.concat({
|
||||
// receiptIds: it.outcome.receipt_ids,
|
||||
// logs: it.outcome.logs,
|
||||
// failure:
|
||||
// typeof it.outcome.status.Failure != "undefined"
|
||||
// ? it.outcome.status.Failure
|
||||
// : null,
|
||||
// });
|
||||
// } else return acc;
|
||||
// }, []);
|
||||
//
|
||||
// console.log(flatLogs);
|
||||
//
|
||||
// const { totalGasBurned, totalTokensBurned } = result.receipts_outcome.reduce(
|
||||
// (acc : any, receipt : any) => {
|
||||
// acc.totalGasBurned += receipt.outcome.gas_burnt;
|
||||
// acc.totalTokensBurned += nearAPI.utils.format.formatNearAmount(
|
||||
// receipt.outcome.tokens_burnt
|
||||
// );
|
||||
// return acc;
|
||||
// },
|
||||
// {
|
||||
// totalGasBurned: result.transaction_outcome.outcome.gas_burnt,
|
||||
// totalTokensBurned: nearAPI.utils.format.formatNearAmount(
|
||||
// result.transaction_outcome.outcome.tokens_burnt
|
||||
// ),
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// console.log(
|
||||
// "totalGasBurned",
|
||||
// totalGasBurned,
|
||||
// "totalTokensBurned",
|
||||
// totalTokensBurned
|
||||
// );
|
||||
//
|
||||
// console.log("result: ", nearAPI.providers.getTransactionLastResult(result));
|
||||
//
|
||||
// console.log(JSON.stringify(result, null, 2));
|
||||
//
|
||||
// {
|
||||
// let out = []
|
||||
// for (const idx in result.receipts_outcome) {
|
||||
// let r = result.receipts_outcome[idx];
|
||||
// out.push({
|
||||
// executor: r.outcome.executor_id,
|
||||
// gas: r.outcome.gas_burnt,
|
||||
// token: r.outcome.tokens_burnt,
|
||||
// logs: r.outcome.logs
|
||||
// });
|
||||
// }
|
||||
// console.log(JSON.stringify(out, null, 2));
|
||||
// }
|
||||
|
||||
console.log("test complete");
|
||||
}
|
||||
|
||||
test();
|
|
@ -0,0 +1,284 @@
|
|||
// npx pretty-quick
|
||||
|
||||
const sha256 = require("js-sha256");
|
||||
const nearAPI = require("near-api-js");
|
||||
const BN = require("bn.js");
|
||||
const fs = require("fs").promises;
|
||||
const assert = require("assert").strict;
|
||||
const fetch = require("node-fetch");
|
||||
const elliptic = require("elliptic");
|
||||
const web3Utils = require("web3-utils");
|
||||
import { zeroPad } from "@ethersproject/bytes";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
|
||||
import { TestLib } from "./testlib";
|
||||
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_NEAR,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/utils";
|
||||
|
||||
import { _parseVAAAlgorand } from "@certusone/wormhole-sdk/lib/cjs/algorand";
|
||||
|
||||
import { getSignedVAAWithRetry } from "@certusone/wormhole-sdk";
|
||||
|
||||
function getConfig(env: any) {
|
||||
switch (env) {
|
||||
case "sandbox":
|
||||
case "local":
|
||||
return {
|
||||
networkId: "sandbox",
|
||||
nodeUrl: "http://localhost:3030",
|
||||
masterAccount: "test.near",
|
||||
wormholeAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "wormhole.test.near",
|
||||
tokenAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "token.test.near",
|
||||
testAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "test.test.near",
|
||||
userAccount:
|
||||
Math.floor(Math.random() * 10000).toString() + "user.test.near",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const wormholeMethods = {
|
||||
viewMethods: [],
|
||||
changeMethods: ["boot_wormhole", "submit_vaa"],
|
||||
};
|
||||
|
||||
const tokenMethods = {
|
||||
viewMethods: [],
|
||||
changeMethods: [
|
||||
"boot_portal",
|
||||
"submit_vaa",
|
||||
"submit_vaa_callback",
|
||||
"attest_near",
|
||||
"attest_token",
|
||||
"send_transfer_near",
|
||||
"send_transfer_wormhole_token",
|
||||
"account_hash",
|
||||
],
|
||||
};
|
||||
|
||||
const testMethods = {
|
||||
viewMethods: [],
|
||||
changeMethods: ["deploy_ft"],
|
||||
};
|
||||
|
||||
const ftMethods = {
|
||||
viewMethods: [],
|
||||
changeMethods: ["ft_transfer_call", "storage_deposit"],
|
||||
};
|
||||
|
||||
let config: any;
|
||||
let masterAccount: any;
|
||||
let _tokenAccount: any;
|
||||
let _wormholeAccount: any;
|
||||
let _testAccount: any;
|
||||
let masterKey: any;
|
||||
let masterPubKey: any;
|
||||
let keyStore: any;
|
||||
let near: any;
|
||||
|
||||
let userAccount: any;
|
||||
let userKey: any;
|
||||
let userPubKey: any;
|
||||
|
||||
async function initNear() {
|
||||
config = getConfig(process.env.NEAR_ENV || "sandbox");
|
||||
|
||||
// Retrieve the validator key directly in the Tilt environment
|
||||
const response = await fetch("http://localhost:3031/validator_key.json");
|
||||
|
||||
const keyFile = await response.json();
|
||||
|
||||
console.log(keyFile);
|
||||
|
||||
masterKey = nearAPI.utils.KeyPair.fromString(
|
||||
keyFile.secret_key || keyFile.private_key
|
||||
);
|
||||
masterPubKey = masterKey.getPublicKey();
|
||||
|
||||
userKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
console.log(userKey);
|
||||
|
||||
keyStore = new nearAPI.keyStores.InMemoryKeyStore();
|
||||
|
||||
keyStore.setKey(config.networkId, config.masterAccount, masterKey);
|
||||
keyStore.setKey(config.networkId, config.userAccount, userKey);
|
||||
|
||||
near = await nearAPI.connect({
|
||||
deps: {
|
||||
keyStore,
|
||||
},
|
||||
networkId: config.networkId,
|
||||
nodeUrl: config.nodeUrl,
|
||||
});
|
||||
masterAccount = new nearAPI.Account(near.connection, config.masterAccount);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR masterAccount: " +
|
||||
JSON.stringify(await masterAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
let resp = await masterAccount.createAccount(
|
||||
config.userAccount,
|
||||
userKey.getPublicKey(),
|
||||
new BN(10).pow(new BN(25))
|
||||
);
|
||||
|
||||
console.log(resp);
|
||||
|
||||
userAccount = new nearAPI.Account(near.connection, config.userAccount);
|
||||
|
||||
console.log(
|
||||
"Finish init NEAR userAccount: " +
|
||||
JSON.stringify(await userAccount.getAccountBalance())
|
||||
);
|
||||
|
||||
// console.log(await userAccount.sendMoney(config.masterAccount, nearAPI.utils.format.parseNearAmount("1.5")));;
|
||||
// console.log("Sent some money: " + JSON.stringify(await userAccount.getAccountBalance()));
|
||||
}
|
||||
|
||||
async function createContractUser(
|
||||
accountPrefix: any,
|
||||
contractAccountId: any,
|
||||
methods: any
|
||||
) {
|
||||
let accountId =
|
||||
Math.floor(Math.random() * 10000).toString() +
|
||||
accountPrefix +
|
||||
"." +
|
||||
config.masterAccount;
|
||||
|
||||
console.log(accountId);
|
||||
|
||||
let randomKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
|
||||
let resp = await masterAccount.createAccount(
|
||||
accountId,
|
||||
randomKey.getPublicKey(),
|
||||
new BN(10).pow(new BN(28))
|
||||
);
|
||||
console.log("accountId: " + JSON.stringify(resp));
|
||||
|
||||
keyStore.setKey(config.networkId, accountId, randomKey);
|
||||
const account = new nearAPI.Account(near.connection, accountId);
|
||||
const accountUseContract = new nearAPI.Contract(
|
||||
account,
|
||||
contractAccountId,
|
||||
methods
|
||||
);
|
||||
return accountUseContract;
|
||||
}
|
||||
|
||||
async function initTest() {
|
||||
const wormholeContract = await fs.readFile(
|
||||
"./contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm"
|
||||
);
|
||||
const tokenContract = await fs.readFile(
|
||||
"./contracts/portal/target/wasm32-unknown-unknown/release/near_token_bridge.wasm"
|
||||
);
|
||||
const testContract = await fs.readFile(
|
||||
"./contracts/mock-bridge-integration/target/wasm32-unknown-unknown/release/near_mock_bridge_integration.wasm"
|
||||
);
|
||||
|
||||
let randomKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(config.networkId, config.wormholeAccount, randomKey);
|
||||
|
||||
_wormholeAccount = await masterAccount.createAndDeployContract(
|
||||
config.wormholeAccount,
|
||||
randomKey.getPublicKey(),
|
||||
wormholeContract,
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
|
||||
randomKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
|
||||
keyStore.setKey(config.networkId, config.tokenAccount, randomKey);
|
||||
|
||||
_tokenAccount = await masterAccount.createAndDeployContract(
|
||||
config.tokenAccount,
|
||||
randomKey.getPublicKey(),
|
||||
tokenContract,
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
|
||||
console.log("tokenAccount: " + config.tokenAccount);
|
||||
|
||||
_testAccount = await masterAccount.createAndDeployContract(
|
||||
config.testAccount,
|
||||
randomKey.getPublicKey(),
|
||||
testContract,
|
||||
new BN(10).pow(new BN(27))
|
||||
);
|
||||
|
||||
const wormholeUseContract = await createContractUser(
|
||||
"wormhole_user",
|
||||
config.wormholeAccount,
|
||||
wormholeMethods
|
||||
);
|
||||
|
||||
console.log("Finish deploy contracts and create test accounts");
|
||||
return {
|
||||
wormholeUseContract,
|
||||
};
|
||||
}
|
||||
|
||||
function delay(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function nearParseResultForLogs(result: any): [number, string] {
|
||||
for (const o of result.receipts_outcome) {
|
||||
for (const l of o.outcome.logs) {
|
||||
console.log(l);
|
||||
if (l.startsWith("EVENT_JSON:")) {
|
||||
const body = JSON.parse(l.slice(11));
|
||||
if (body.standard == "wormhole" && body.event == "publish") {
|
||||
console.log(body);
|
||||
return [body.seq, body.emitter];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [-1, ""];
|
||||
}
|
||||
|
||||
async function test() {
|
||||
let fastTest = true;
|
||||
let ts = new TestLib();
|
||||
|
||||
let vaa =
|
||||
"01000000000100eea5fa9ff5e88efeec83febdb6281a23152fe176d76e0f625781c93636e73ec309530f0fdebb24f410f255215d3b0f9fcb2cfafce3f9a8b97ac6640b32d24c8401000000010000000100010000000000000000000000000000000000000000000000000000000000000004000000000315346a00000000000000000000000000000000000000000000546f6b656e4272696467650100000001c69a1b1a65dd336bf1df6a77afb501fc25db7fc0938cb08595a9ef473265cb4f";
|
||||
|
||||
let p = _parseVAAAlgorand(new Uint8Array(Buffer.from(vaa, "hex")));
|
||||
console.log(ts.uint8ArrayToHexString(p.targetEmitter as Uint8Array, false));
|
||||
|
||||
|
||||
await initNear();
|
||||
const { wormholeUseContract } = await initTest();
|
||||
|
||||
console.log("Booting guardian set with index 0");
|
||||
|
||||
console.log(ts.singleGuardianKey);
|
||||
|
||||
await wormholeUseContract.boot_wormhole({
|
||||
args: { gset: 0, addresses: ts.singleGuardianKey },
|
||||
});
|
||||
console.log("Completed without an error... odd.. I am not sucking yet");
|
||||
|
||||
let result = await userAccount.functionCall({
|
||||
contractId: config.wormholeAccount,
|
||||
methodName: "verify_vaa",
|
||||
args: {
|
||||
vaa,
|
||||
},
|
||||
gas: 100000000000000,
|
||||
});
|
||||
|
||||
console.log("test complete");
|
||||
}
|
||||
|
||||
test();
|
|
@ -0,0 +1,566 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2022 Wormhole Project Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_BSC,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_FANTOM,
|
||||
CHAIN_ID_OASIS,
|
||||
CHAIN_ID_POLYGON,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_NEAR,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/utils";
|
||||
|
||||
const web3EthAbi = require("web3-eth-abi");
|
||||
const web3Utils = require("web3-utils");
|
||||
const elliptic = require("elliptic");
|
||||
|
||||
export class TestLib {
|
||||
zeroBytes: string;
|
||||
|
||||
singleGuardianKey: string[] = ["beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"];
|
||||
|
||||
singleGuardianPrivKey: string[] = [
|
||||
"cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0",
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.zeroBytes =
|
||||
"0000000000000000000000000000000000000000000000000000000000000000";
|
||||
}
|
||||
|
||||
hexStringToUint8Array(hs: string): Uint8Array {
|
||||
if (hs.length % 2 === 1) {
|
||||
// prepend a 0
|
||||
hs = "0" + hs;
|
||||
}
|
||||
const buf = Buffer.from(hs, "hex");
|
||||
const retval = Uint8Array.from(buf);
|
||||
return retval;
|
||||
}
|
||||
|
||||
uint8ArrayToHexString(arr: Uint8Array, add0x: boolean) {
|
||||
const ret: string = Buffer.from(arr).toString("hex");
|
||||
if (!add0x) {
|
||||
return ret;
|
||||
}
|
||||
return "0x" + ret;
|
||||
}
|
||||
|
||||
guardianKeys: string[] = [
|
||||
"52A26Ce40F8CAa8D36155d37ef0D5D783fc614d2",
|
||||
"389A74E8FFa224aeAD0778c786163a7A2150768C",
|
||||
"B4459EA6482D4aE574305B239B4f2264239e7599",
|
||||
"072491bd66F63356090C11Aae8114F5372aBf12B",
|
||||
"51280eA1fd2B0A1c76Ae29a7d54dda68860A2bfF",
|
||||
"fa9Aa60CfF05e20E2CcAA784eE89A0A16C2057CB",
|
||||
"e42d59F8FCd86a1c5c4bA351bD251A5c5B05DF6A",
|
||||
"4B07fF9D5cE1A6ed58b6e9e7d6974d1baBEc087e",
|
||||
"c8306B84235D7b0478c61783C50F990bfC44cFc0",
|
||||
"C8C1035110a13fe788259A4148F871b52bAbcb1B",
|
||||
"58A2508A20A7198E131503ce26bBE119aA8c62b2",
|
||||
"8390820f04ddA22AFe03be1c3bb10f4ba6CF94A0",
|
||||
"1FD6e97387C34a1F36DE0f8341E9D409E06ec45b",
|
||||
"255a41fC2792209CB998A8287204D40996df9E54",
|
||||
"bA663B12DD23fbF4FbAC618Be140727986B3BBd0",
|
||||
"79040E577aC50486d0F6930e160A5C75FD1203C6",
|
||||
"3580D2F00309A9A85efFAf02564Fc183C0183A96",
|
||||
"3869795913D3B6dBF3B24a1C7654672c69A23c35",
|
||||
"1c0Cc52D7673c52DE99785741344662F5b2308a0",
|
||||
];
|
||||
|
||||
guardianPrivKeys: string[] = [
|
||||
"563d8d2fd4e701901d3846dee7ae7a92c18f1975195264d676f8407ac5976757",
|
||||
"8d97f25916a755df1d9ef74eb4dbebc5f868cb07830527731e94478cdc2b9d5f",
|
||||
"9bd728ad7617c05c31382053b57658d4a8125684c0098f740a054d87ddc0e93b",
|
||||
"5a02c4cd110d20a83a7ce8d1a2b2ae5df252b4e5f6781c7855db5cc28ed2d1b4",
|
||||
"93d4e3b443bf11f99a00901222c032bd5f63cf73fc1bcfa40829824d121be9b2",
|
||||
"ea40e40c63c6ff155230da64a2c44fcd1f1c9e50cacb752c230f77771ce1d856",
|
||||
"87eaabe9c27a82198e618bca20f48f9679c0f239948dbd094005e262da33fe6a",
|
||||
"61ffed2bff38648a6d36d6ed560b741b1ca53d45391441124f27e1e48ca04770",
|
||||
"bd12a242c6da318fef8f98002efb98efbf434218a78730a197d981bebaee826e",
|
||||
"20d3597bb16525b6d09e5fb56feb91b053d961ab156f4807e37d980f50e71aff",
|
||||
"344b313ffbc0199ff6ca08cacdaf5dc1d85221e2f2dc156a84245bd49b981673",
|
||||
"848b93264edd3f1a521274ca4da4632989eb5303fd15b14e5ec6bcaa91172b05",
|
||||
"c6f2046c1e6c172497fc23bd362104e2f4460d0f61984938fa16ef43f27d93f6",
|
||||
"693b256b1ee6b6fb353ba23274280e7166ab3be8c23c203cc76d716ba4bc32bf",
|
||||
"13c41508c0da03018d61427910b9922345ced25e2bbce50652e939ee6e5ea56d",
|
||||
"460ee0ee403be7a4f1eb1c63dd1edaa815fbaa6cf0cf2344dcba4a8acf9aca74",
|
||||
"b25148579b99b18c8994b0b86e4dd586975a78fa6e7ad6ec89478d7fbafd2683",
|
||||
"90d7ac6a82166c908b8cf1b352f3c9340a8d1f2907d7146fb7cd6354a5436cca",
|
||||
"b71d23908e4cf5d6cd973394f3a4b6b164eb1065785feee612efdfd8d30005ed",
|
||||
];
|
||||
|
||||
encoder(type: string, val: any) {
|
||||
if (type == "uint8")
|
||||
return web3EthAbi.encodeParameter("uint8", val).substring(2 + (64 - 2));
|
||||
if (type == "uint16")
|
||||
return web3EthAbi.encodeParameter("uint16", val).substring(2 + (64 - 4));
|
||||
if (type == "uint32")
|
||||
return web3EthAbi.encodeParameter("uint32", val).substring(2 + (64 - 8));
|
||||
if (type == "uint64")
|
||||
return web3EthAbi.encodeParameter("uint64", val).substring(2 + (64 - 16));
|
||||
if (type == "uint128")
|
||||
return web3EthAbi
|
||||
.encodeParameter("uint128", val)
|
||||
.substring(2 + (64 - 32));
|
||||
if (type == "uint256" || type == "bytes32")
|
||||
return web3EthAbi.encodeParameter(type, val).substring(2 + (64 - 64));
|
||||
}
|
||||
|
||||
ord(c: any) {
|
||||
return c.charCodeAt(0);
|
||||
}
|
||||
|
||||
genGuardianSetUpgrade(
|
||||
signers: any,
|
||||
guardianSet: number,
|
||||
targetSet: number,
|
||||
nonce: number,
|
||||
seq: number,
|
||||
guardianKeys: string[]
|
||||
): string {
|
||||
const b = [
|
||||
"0x",
|
||||
this.zeroBytes.slice(0, 28 * 2),
|
||||
this.encoder("uint8", this.ord("C")),
|
||||
this.encoder("uint8", this.ord("o")),
|
||||
this.encoder("uint8", this.ord("r")),
|
||||
this.encoder("uint8", this.ord("e")),
|
||||
this.encoder("uint8", 2),
|
||||
this.encoder("uint16", 0),
|
||||
this.encoder("uint32", targetSet),
|
||||
this.encoder("uint8", guardianKeys.length),
|
||||
];
|
||||
|
||||
guardianKeys.forEach((x) => {
|
||||
b.push(x);
|
||||
});
|
||||
|
||||
let emitter = "0x" + this.zeroBytes.slice(0, 31 * 2) + "04";
|
||||
let seconds = Math.floor(new Date().getTime() / 1000.0);
|
||||
|
||||
return this.createSignedVAA(
|
||||
guardianSet,
|
||||
signers,
|
||||
seconds,
|
||||
nonce,
|
||||
1,
|
||||
emitter,
|
||||
seq,
|
||||
32,
|
||||
b.join("")
|
||||
);
|
||||
}
|
||||
|
||||
genGSetFee(
|
||||
signers: any,
|
||||
guardianSet: number,
|
||||
nonce: number,
|
||||
seq: number,
|
||||
tchain: number,
|
||||
amt: number
|
||||
) {
|
||||
const b = [
|
||||
"0x",
|
||||
this.zeroBytes.slice(0, 28 * 2),
|
||||
this.encoder("uint8", this.ord("C")),
|
||||
this.encoder("uint8", this.ord("o")),
|
||||
this.encoder("uint8", this.ord("r")),
|
||||
this.encoder("uint8", this.ord("e")),
|
||||
this.encoder("uint8", 3),
|
||||
this.encoder("uint16", tchain),
|
||||
this.encoder("uint256", Math.floor(amt)),
|
||||
];
|
||||
|
||||
let emitter = "0x" + this.zeroBytes.slice(0, 31 * 2) + "04";
|
||||
|
||||
var seconds = Math.floor(new Date().getTime() / 1000.0);
|
||||
|
||||
return this.createSignedVAA(
|
||||
guardianSet,
|
||||
signers,
|
||||
seconds,
|
||||
nonce,
|
||||
1,
|
||||
emitter,
|
||||
seq,
|
||||
32,
|
||||
b.join("")
|
||||
);
|
||||
}
|
||||
|
||||
genGFeePayout(
|
||||
signers: any,
|
||||
guardianSet: number,
|
||||
nonce: number,
|
||||
seq: number,
|
||||
tchain: number,
|
||||
amt: number,
|
||||
dest: Uint8Array
|
||||
) {
|
||||
const b = [
|
||||
"0x",
|
||||
this.zeroBytes.slice(0, 28 * 2),
|
||||
this.encoder("uint8", this.ord("C")),
|
||||
this.encoder("uint8", this.ord("o")),
|
||||
this.encoder("uint8", this.ord("r")),
|
||||
this.encoder("uint8", this.ord("e")),
|
||||
this.encoder("uint8", 4),
|
||||
this.encoder("uint16", tchain),
|
||||
this.encoder("uint256", Math.floor(amt)),
|
||||
this.uint8ArrayToHexString(dest, false),
|
||||
];
|
||||
|
||||
let emitter = "0x" + this.zeroBytes.slice(0, 31 * 2) + "04";
|
||||
|
||||
var seconds = Math.floor(new Date().getTime() / 1000.0);
|
||||
|
||||
return this.createSignedVAA(
|
||||
guardianSet,
|
||||
signers,
|
||||
seconds,
|
||||
nonce,
|
||||
1,
|
||||
emitter,
|
||||
seq,
|
||||
32,
|
||||
b.join("")
|
||||
);
|
||||
}
|
||||
|
||||
getTokenEmitter(chain: number): string {
|
||||
if (chain === CHAIN_ID_SOLANA) {
|
||||
return "c69a1b1a65dd336bf1df6a77afb501fc25db7fc0938cb08595a9ef473265cb4f";
|
||||
}
|
||||
if (chain === CHAIN_ID_ETH) {
|
||||
return "0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585";
|
||||
}
|
||||
if (chain === CHAIN_ID_TERRA) {
|
||||
return "0000000000000000000000007cf7b764e38a0a5e967972c1df77d432510564e2";
|
||||
}
|
||||
if (chain === CHAIN_ID_BSC) {
|
||||
return "000000000000000000000000b6f6d86a8f9879a9c87f643768d9efc38c1da6e7";
|
||||
}
|
||||
if (chain === CHAIN_ID_POLYGON) {
|
||||
return "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde";
|
||||
}
|
||||
if (chain === CHAIN_ID_AVAX) {
|
||||
return "0000000000000000000000000e082f06ff657d94310cb8ce8b0d9a04541d8052";
|
||||
}
|
||||
if (chain === CHAIN_ID_OASIS) {
|
||||
return "0000000000000000000000005848c791e09901b40a9ef749f2a6735b418d7564";
|
||||
}
|
||||
if (chain === CHAIN_ID_FANTOM) {
|
||||
return "0000000000000000000000007C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
getNftEmitter(chain: ChainId): string {
|
||||
if (chain === CHAIN_ID_SOLANA) {
|
||||
return "96ee982293251b48729804c8e8b24b553eb6b887867024948d2236fd37a577ab";
|
||||
}
|
||||
if (chain === CHAIN_ID_ETH) {
|
||||
return "0000000000000000000000006ffd7ede62328b3af38fcd61461bbfc52f5651fe";
|
||||
}
|
||||
if (chain === CHAIN_ID_BSC) {
|
||||
return "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde";
|
||||
}
|
||||
if (chain === CHAIN_ID_POLYGON) {
|
||||
return "00000000000000000000000090bbd86a6fe93d3bc3ed6335935447e75fab7fcf";
|
||||
}
|
||||
if (chain === CHAIN_ID_AVAX) {
|
||||
return "000000000000000000000000f7b6737ca9c4e08ae573f75a97b73d7a813f5de5";
|
||||
}
|
||||
if (chain === CHAIN_ID_OASIS) {
|
||||
return "00000000000000000000000004952D522Ff217f40B5Ef3cbF659EcA7b952a6c1";
|
||||
}
|
||||
if (chain === CHAIN_ID_FANTOM) {
|
||||
return "000000000000000000000000A9c7119aBDa80d4a4E0C06C8F4d8cF5893234535";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
genRegisterChain(
|
||||
signers: any,
|
||||
guardianSet: number,
|
||||
nonce: number,
|
||||
seq: number,
|
||||
chain: number
|
||||
) {
|
||||
const b = [
|
||||
"0x",
|
||||
this.zeroBytes.slice(0, (32 - 11) * 2),
|
||||
this.encoder("uint8", this.ord("T")),
|
||||
this.encoder("uint8", this.ord("o")),
|
||||
this.encoder("uint8", this.ord("k")),
|
||||
this.encoder("uint8", this.ord("e")),
|
||||
this.encoder("uint8", this.ord("n")),
|
||||
this.encoder("uint8", this.ord("B")),
|
||||
this.encoder("uint8", this.ord("r")),
|
||||
this.encoder("uint8", this.ord("i")),
|
||||
this.encoder("uint8", this.ord("d")),
|
||||
this.encoder("uint8", this.ord("g")),
|
||||
this.encoder("uint8", this.ord("e")),
|
||||
this.encoder("uint8", 1),
|
||||
this.encoder("uint16", 0),
|
||||
this.encoder("uint16", chain),
|
||||
this.getTokenEmitter(chain),
|
||||
];
|
||||
let emitter = "0x" + this.zeroBytes.slice(0, 31 * 2) + "04";
|
||||
|
||||
var seconds = Math.floor(new Date().getTime() / 1000.0);
|
||||
|
||||
return this.createSignedVAA(
|
||||
guardianSet,
|
||||
signers,
|
||||
seconds,
|
||||
nonce,
|
||||
1,
|
||||
emitter,
|
||||
seq,
|
||||
32,
|
||||
b.join("")
|
||||
);
|
||||
}
|
||||
|
||||
genAssetMeta(
|
||||
signers: any,
|
||||
guardianSet: number,
|
||||
nonce: number,
|
||||
seq: number,
|
||||
tokenAddress: string,
|
||||
chain: number,
|
||||
decimals: number,
|
||||
symbol: string,
|
||||
name: string
|
||||
) {
|
||||
const b = [
|
||||
"0x",
|
||||
this.encoder("uint8", 2),
|
||||
this.zeroBytes.slice(0, 64 - tokenAddress.length),
|
||||
tokenAddress,
|
||||
this.encoder("uint16", chain),
|
||||
this.encoder("uint8", decimals),
|
||||
Buffer.from(symbol).toString("hex"),
|
||||
this.zeroBytes.slice(0, (32 - symbol.length) * 2),
|
||||
Buffer.from(name).toString("hex"),
|
||||
this.zeroBytes.slice(0, (32 - name.length) * 2),
|
||||
];
|
||||
|
||||
// console.log(b.join())
|
||||
// console.log(b.join('').length)
|
||||
|
||||
let emitter = "0x" + this.getTokenEmitter(chain);
|
||||
let seconds = Math.floor(new Date().getTime() / 1000.0);
|
||||
|
||||
return this.createSignedVAA(
|
||||
guardianSet,
|
||||
signers,
|
||||
seconds,
|
||||
nonce,
|
||||
chain,
|
||||
emitter,
|
||||
seq,
|
||||
32,
|
||||
b.join("")
|
||||
);
|
||||
}
|
||||
|
||||
genTransfer(
|
||||
signers: any,
|
||||
guardianSet: number,
|
||||
nonce: number,
|
||||
seq: number,
|
||||
amount: number,
|
||||
tokenAddress: string,
|
||||
tokenChain: number,
|
||||
toAddress: string,
|
||||
toChain: number,
|
||||
fee: number
|
||||
) {
|
||||
const b = [
|
||||
"0x",
|
||||
this.encoder("uint8", 1),
|
||||
this.encoder("uint256", Math.floor(amount * 100000000)),
|
||||
this.zeroBytes.slice(0, 64 - tokenAddress.length),
|
||||
tokenAddress,
|
||||
this.encoder("uint16", tokenChain),
|
||||
this.zeroBytes.slice(0, 64 - toAddress.length),
|
||||
toAddress,
|
||||
this.encoder("uint16", toChain),
|
||||
this.encoder("uint256", Math.floor(fee * 100000000)),
|
||||
];
|
||||
|
||||
let emitter = "0x" + this.getTokenEmitter(tokenChain);
|
||||
let seconds = Math.floor(new Date().getTime() / 1000.0);
|
||||
|
||||
return this.createSignedVAA(
|
||||
guardianSet,
|
||||
signers,
|
||||
seconds,
|
||||
nonce,
|
||||
tokenChain,
|
||||
emitter,
|
||||
seq,
|
||||
32,
|
||||
b.join("")
|
||||
);
|
||||
}
|
||||
|
||||
genNFTTransfer(
|
||||
signers: any,
|
||||
guardianSet: number,
|
||||
nonce: number,
|
||||
seq: number,
|
||||
contract: string, // 32 bytes
|
||||
fchain: number,
|
||||
symbol: string,
|
||||
name: string,
|
||||
tokenid: string,
|
||||
uri: string,
|
||||
target: string, // 32 bytes
|
||||
tochain: number
|
||||
) {
|
||||
const b = [
|
||||
"0x",
|
||||
this.encoder("uint8", 1),
|
||||
|
||||
this.zeroBytes.slice(0, 64 - contract.length),
|
||||
contract,
|
||||
|
||||
this.encoder("uint16", fchain),
|
||||
|
||||
Buffer.from(symbol).toString("hex"),
|
||||
this.zeroBytes.slice(0, (32 - symbol.length) * 2),
|
||||
|
||||
Buffer.from(name).toString("hex"),
|
||||
this.zeroBytes.slice(0, (32 - name.length) * 2),
|
||||
|
||||
this.zeroBytes.slice(0, 64 - tokenid.length),
|
||||
tokenid,
|
||||
|
||||
this.encoder("uint8", uri.length),
|
||||
Buffer.from(uri).toString("hex"),
|
||||
|
||||
this.zeroBytes.slice(0, 64 - target.length),
|
||||
target,
|
||||
|
||||
this.encoder("uint16", tochain),
|
||||
];
|
||||
|
||||
let emitter = "0x" + this.getNftEmitter(fchain as ChainId);
|
||||
let seconds = Math.floor(new Date().getTime() / 1000.0);
|
||||
|
||||
return this.createSignedVAA(
|
||||
guardianSet,
|
||||
signers,
|
||||
seconds,
|
||||
nonce,
|
||||
fchain,
|
||||
emitter,
|
||||
seq,
|
||||
32,
|
||||
b.join("")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a packed and signed VAA for testing.
|
||||
* See https://github.com/certusone/wormhole/blob/dev.v2/design/0001_generic_message_passing.md
|
||||
*
|
||||
* @param {} guardianSetIndex The guardian set index
|
||||
* @param {*} signers The list of private keys for signing the VAA
|
||||
* @param {*} timestamp The timestamp of VAA
|
||||
* @param {*} nonce The nonce.
|
||||
* @param {*} emitterChainId The emitter chain identifier
|
||||
* @param {*} emitterAddress The emitter chain address, prefixed with 0x
|
||||
* @param {*} sequence The sequence.
|
||||
* @param {*} consistencyLevel The reported consistency level
|
||||
* @param {*} payload This VAA Payload hex string, prefixed with 0x
|
||||
*/
|
||||
createSignedVAA(
|
||||
guardianSetIndex: number,
|
||||
signers: any,
|
||||
timestamp: number,
|
||||
nonce: number,
|
||||
emitterChainId: number,
|
||||
emitterAddress: string,
|
||||
sequence: number,
|
||||
consistencyLevel: number,
|
||||
payload: string
|
||||
) {
|
||||
console.log(typeof payload);
|
||||
|
||||
const body = [
|
||||
this.encoder("uint32", timestamp),
|
||||
this.encoder("uint32", nonce),
|
||||
this.encoder("uint16", emitterChainId),
|
||||
this.encoder("bytes32", emitterAddress),
|
||||
this.encoder("uint64", sequence),
|
||||
this.encoder("uint8", consistencyLevel),
|
||||
payload.substring(2),
|
||||
];
|
||||
|
||||
const hash = web3Utils.keccak256(web3Utils.keccak256("0x" + body.join("")));
|
||||
|
||||
let signatures = "";
|
||||
|
||||
for (const i in signers) {
|
||||
// eslint-disable-next-line new-cap
|
||||
const ec = new elliptic.ec("secp256k1");
|
||||
const key = ec.keyFromPrivate(signers[i]);
|
||||
const signature = key.sign(hash.substr(2), { canonical: true });
|
||||
|
||||
const packSig = [
|
||||
this.encoder("uint8", i),
|
||||
this.zeroPadBytes(signature.r.toString(16), 32),
|
||||
this.zeroPadBytes(signature.s.toString(16), 32),
|
||||
this.encoder("uint8", signature.recoveryParam),
|
||||
];
|
||||
|
||||
signatures += packSig.join("");
|
||||
}
|
||||
|
||||
const vm = [
|
||||
this.encoder("uint8", 1),
|
||||
this.encoder("uint32", guardianSetIndex),
|
||||
this.encoder("uint8", signers.length),
|
||||
|
||||
signatures,
|
||||
body.join(""),
|
||||
].join("");
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
zeroPadBytes(value: string, length: number) {
|
||||
while (value.length < 2 * length) {
|
||||
value = "0" + value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TestLib,
|
||||
};
|
|
@ -0,0 +1,164 @@
|
|||
// http --verbose post http://localhost:3030 jsonrpc=2.0 id=dontcare method=block params:='{"block_id": 5004}'
|
||||
// http --verbose post http://localhost:3030 jsonrpc=2.0 id=dontcare method=chunk params:='{"chunk_id": "AgVjJBCy5LBq9UBJuT1ZEhPJaY8DzUrpxbXGHHkqQkCb"}'
|
||||
// http --verbose post http://localhost:3030 jsonrpc=2.0 id=dontcare method=EXPERIMENTAL_tx_status params:='["HZsEBFyo5fiRhApFx4SNm7Ao8anfdEiUyS3cVUf8riG1", "7144wormhole.test.near"]'
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/tidwall/gjson"
|
||||
// "encoding/base64"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
// "encoding/json"
|
||||
)
|
||||
|
||||
func getTxStatus(tx string, src string) ([]byte, error) {
|
||||
s := fmt.Sprintf(`{"id": "dontcare", "jsonrpc": "2.0", "method": "EXPERIMENTAL_tx_status", "params": ["%s", "%s"]}`, tx, src)
|
||||
fmt.Printf("%s\n", s)
|
||||
|
||||
resp, err := http.Post("http://localhost:3030", "application/json", bytes.NewBuffer([]byte(s)))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func getBlock(block uint64) ([]byte, error) {
|
||||
s := fmt.Sprintf(`{"id": "dontcare", "jsonrpc": "2.0", "method": "block", "params": {"block_id": %d}}`, block)
|
||||
resp, err := http.Post("http://localhost:3030", "application/json", bytes.NewBuffer([]byte(s)))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func getFinalBlock() ([]byte, error) {
|
||||
s := fmt.Sprintf(`{"id": "dontcare", "jsonrpc": "2.0", "method": "block", "params": {"finality": "final"}}`)
|
||||
resp, err := http.Post("http://localhost:3030", "application/json", bytes.NewBuffer([]byte(s)))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func getChunk(chunk string) ([]byte, error) {
|
||||
s := fmt.Sprintf(`{"id": "dontcare", "jsonrpc": "2.0", "method": "chunk", "params": {"chunk_id": "%s"}}`, chunk)
|
||||
|
||||
resp, err := http.Post("http://localhost:3030", "application/json", bytes.NewBuffer([]byte(s)))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func inspectBody(block uint64, body gjson.Result) error {
|
||||
fmt.Printf("block %d\n", block)
|
||||
|
||||
result := body.Get("result.chunks.#.chunk_hash")
|
||||
if !result.Exists() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, name := range result.Array() {
|
||||
chunk, err := getChunk(name.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txns := gjson.ParseBytes(chunk).Get("result.transactions")
|
||||
if !txns.Exists() {
|
||||
continue
|
||||
}
|
||||
for _, r := range txns.Array() {
|
||||
hash := r.Get("hash")
|
||||
receiver_id := r.Get("receiver_id")
|
||||
if !hash.Exists() || !receiver_id.Exists() {
|
||||
continue
|
||||
}
|
||||
|
||||
t, _ := getTxStatus(hash.String(), receiver_id.String())
|
||||
fmt.Printf("outcome: %s\n", t)
|
||||
|
||||
outcomes := gjson.ParseBytes(t).Get("result.receipts_outcome")
|
||||
|
||||
if !outcomes.Exists() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, o := range outcomes.Array() {
|
||||
outcome := o.Get("outcome")
|
||||
if !outcome.Exists() {
|
||||
continue
|
||||
}
|
||||
|
||||
executor_id := outcome.Get("executor_id")
|
||||
if !executor_id.Exists() {
|
||||
continue
|
||||
}
|
||||
if executor_id.String() == "wormhole.test.near" {
|
||||
l := outcome.Get("logs")
|
||||
if !l.Exists() {
|
||||
continue
|
||||
}
|
||||
for _, log := range l.Array() {
|
||||
event := log.String()
|
||||
if !strings.HasPrefix(event, "EVENT_JSON:") {
|
||||
continue
|
||||
}
|
||||
// event_json := gjson.ParseBytes(event[11:])
|
||||
fmt.Printf("log: %s\n", event[11:])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
finalBody, _ := getFinalBlock()
|
||||
block := gjson.ParseBytes(finalBody).Get("result.chunks.0.height_created").Uint()
|
||||
|
||||
for {
|
||||
finalBody, err := getFinalBlock()
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
} else {
|
||||
parsedFinalBody := gjson.ParseBytes(finalBody)
|
||||
lastBlock := parsedFinalBody.Get("result.chunks.0.height_created").Uint()
|
||||
|
||||
for ; block <= lastBlock; block = block + 1 {
|
||||
if block == lastBlock {
|
||||
inspectBody(block, parsedFinalBody)
|
||||
} else {
|
||||
b, err := getBlock(block)
|
||||
// fmt.Printf("block: %s\n", b);
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
break
|
||||
} else {
|
||||
inspectBody(block, gjson.ParseBytes(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
|
@ -203,6 +203,12 @@
|
|||
"tokenBridgeEmitterAddress": "8edf5b0e108c3a1a0a4b704cc89591f2ad8d50df24e991567e640ed720a94be2"
|
||||
}
|
||||
},
|
||||
"15": {
|
||||
"contracts": {
|
||||
"tokenBridgeEmitterAddress": "e83c99874cb2d60921648a438606f5ffcf60c2e26ef13678b2e57fab3def6a30",
|
||||
"nftBridgeEmitterAddress": "11aaad1c851095bf97ee1be9ed1161a47187aba8b71bf6821efac391d8ee95f7"
|
||||
}
|
||||
},
|
||||
"18": {
|
||||
"rpcUrlTilt": "http://terra2-terrad:1317",
|
||||
"rpcUrlLocal": "http://localhost:1318",
|
||||
|
|
|
@ -85,11 +85,13 @@ ethTokenBridge=$(jq --raw-output '.chains."2".contracts.tokenBridgeEmitterAddres
|
|||
terraTokenBridge=$(jq --raw-output '.chains."3".contracts.tokenBridgeEmitterAddress' $addressesJson)
|
||||
bscTokenBridge=$(jq --raw-output '.chains."4".contracts.tokenBridgeEmitterAddress' $addressesJson)
|
||||
algoTokenBridge=$(jq --raw-output '.chains."8".contracts.tokenBridgeEmitterAddress' $addressesJson)
|
||||
nearTokenBridge=$(jq --raw-output '.chains."15".contracts.tokenBridgeEmitterAddress' $addressesJson)
|
||||
terra2TokenBridge=$(jq --raw-output '.chains."18".contracts.tokenBridgeEmitterAddress' $addressesJson)
|
||||
|
||||
solNFTBridge=$(jq --raw-output '.chains."1".contracts.nftBridgeEmitterAddress' $addressesJson)
|
||||
ethNFTBridge=$(jq --raw-output '.chains."2".contracts.nftBridgeEmitterAddress' $addressesJson)
|
||||
terraNFTBridge=$(jq --raw-output '.chains."3".contracts.nftBridgeEmitterAddress' $addressesJson)
|
||||
nearNFTBridge=$(jq --raw-output '.chains."15".contracts.nftBridgeEmitterAddress' $addressesJson)
|
||||
|
||||
# 4) create token bridge registration VAAs
|
||||
# invoke CLI commands to create registration VAAs
|
||||
|
@ -98,6 +100,7 @@ ethTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m Tok
|
|||
terraTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c terra -a ${terraTokenBridge} -g ${guardiansPrivateCSV})
|
||||
bscTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c bsc -a ${bscTokenBridge} -g ${guardiansPrivateCSV})
|
||||
algoTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c algorand -a ${algoTokenBridge} -g ${guardiansPrivateCSV})
|
||||
nearTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c near -a ${nearTokenBridge} -g ${guardiansPrivateCSV})
|
||||
terra2TokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c terra2 -a ${terra2TokenBridge} -g ${guardiansPrivateCSV})
|
||||
|
||||
|
||||
|
@ -106,7 +109,7 @@ echo "generating contract registration VAAs for nft bridges"
|
|||
solNFTBridgeVAA=$(node ./clients/js/build/main.js generate registration -m NFTBridge -c solana -a ${solNFTBridge} -g ${guardiansPrivateCSV})
|
||||
ethNFTBridgeVAA=$(node ./clients/js/build/main.js generate registration -m NFTBridge -c ethereum -a ${ethNFTBridge} -g ${guardiansPrivateCSV})
|
||||
terraNFTBridgeVAA=$(node ./clients/js/build/main.js generate registration -m NFTBridge -c terra -a ${terraNFTBridge} -g ${guardiansPrivateCSV})
|
||||
|
||||
nearNFTBridgeVAA=$(node ./clients/js/build/main.js generate registration -m NFTBridge -c near -a ${nearNFTBridge} -g ${guardiansPrivateCSV})
|
||||
|
||||
|
||||
# 6) write the registration VAAs to env files
|
||||
|
@ -118,10 +121,12 @@ terraTokenBridge="REGISTER_TERRA_TOKEN_BRIDGE_VAA"
|
|||
bscTokenBridge="REGISTER_BSC_TOKEN_BRIDGE_VAA"
|
||||
algoTokenBridge="REGISTER_ALGO_TOKEN_BRIDGE_VAA"
|
||||
terra2TokenBridge="REGISTER_TERRA2_TOKEN_BRIDGE_VAA"
|
||||
nearTokenBridge="REGISTER_NEAR_TOKEN_BRIDGE_VAA"
|
||||
|
||||
solNFTBridge="REGISTER_SOL_NFT_BRIDGE_VAA"
|
||||
ethNFTBridge="REGISTER_ETH_NFT_BRIDGE_VAA"
|
||||
terraNFTBridge="REGISTER_TERRA_NFT_BRIDGE_VAA"
|
||||
nearNFTBridge="REGISTER_NEAR_NFT_BRIDGE_VAA"
|
||||
|
||||
|
||||
# solana token bridge
|
||||
|
@ -160,6 +165,12 @@ upsert_env_file $envFile $algoTokenBridge $algoTokenBridgeVAA
|
|||
upsert_env_file $ethFile $terra2TokenBridge $terra2TokenBridgeVAA
|
||||
upsert_env_file $envFile $terra2TokenBridge $terra2TokenBridgeVAA
|
||||
|
||||
# near token bridge
|
||||
upsert_env_file $ethFile $nearTokenBridge $nearTokenBridgeVAA
|
||||
upsert_env_file $envFile $nearTokenBridge $nearTokenBridgeVAA
|
||||
# near nft bridge
|
||||
upsert_env_file $ethFile $nearNFTBridge $nearNFTBridgeVAA
|
||||
upsert_env_file $envFile $nearNFTBridge $nearNFTBridgeVAA
|
||||
|
||||
# 7) copy the local .env file to the solana & terra dirs, if the script is running on the host machine
|
||||
# chain dirs will not exist if running in docker for Tilt, only if running locally. check before copying.
|
||||
|
@ -170,7 +181,7 @@ if [[ -d ./ethereum ]]; then
|
|||
fi
|
||||
|
||||
# copy the hex envFile to each of the non-EVM chains
|
||||
for envDest in ./solana/.env ./terra/tools/.env ./cosmwasm/tools/.env ./algorand/.env; do
|
||||
for envDest in ./solana/.env ./terra/tools/.env ./cosmwasm/tools/.env ./algorand/.env ./near/.env; do
|
||||
dirname=$(dirname $envDest)
|
||||
if [[ -d "$dirname" ]]; then
|
||||
echo "copying $envFile to $envDest"
|
||||
|
|
Loading…
Reference in New Issue