near/audit2: more audit cleanup

This commit is contained in:
Josh Siegel 2022-08-25 09:54:03 -05:00 committed by jumpsiegel
parent dc28f88218
commit 0fc3e8badc
11 changed files with 379 additions and 230 deletions

14
near/Dockerfile.build Normal file
View File

@ -0,0 +1,14 @@
FROM debian@sha256:2ce44bbc00a79113c296d9d25524e15d423b23303fdbbe20190d2f96e0aeb251 as near-contracts-build
RUN apt-get update && apt-get install apt-utils && apt-get install -y python3 npm curl --no-install-recommends
RUN apt-get install -y build-essential git
ADD . .
RUN ./setup-rust.sh
RUN ./build-contracts.sh
FROM scratch AS near-contracts-export
COPY --from=near-contracts-build /contracts/token-bridge/target/wasm32-unknown-unknown/release/near_token_bridge.wasm near_token_bridge.wasm
COPY --from=near-contracts-build /contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm near_wormhole.wasm

View File

@ -4,6 +4,14 @@ test: build node_modules
all: node_modules build nearcore
.PHONY: artifacts
artifacts:
mkdir -p $@
@echo "Building artifacts for near"
DOCKER_BUILDKIT=1 docker build -f Dockerfile.build -t near-builder -o type=local,dest=$@ .
cd $@ && ls | xargs sha256sum > checksums.txt
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 \

View File

@ -1,155 +0,0 @@
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,
)

View File

@ -1,8 +1,2 @@
-- 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...

5
near/build-contracts.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
source $HOME/.cargo/env
make build

View File

@ -587,7 +587,9 @@ impl TokenBridge {
file!(),
line!()
));
ext_ft_contract::ext(asset_token_account.clone()).update_ft(
ext_ft_contract::ext(asset_token_account.clone())
.with_static_gas(Gas(10_000_000_000_000))
.update_ft(
ft,
data.to_vec(),
vaa.sequence,
@ -802,7 +804,7 @@ impl TokenBridge {
Promise::new(refund_to).transfer(env::attached_deposit());
return ret;
}
let a = AccountId::new_unchecked(account);
let a = AccountId::try_from(account).unwrap();
self.hash_map.insert(&account_hash, &a);
if env::storage_usage() < storage_used {
@ -1652,32 +1654,30 @@ impl TokenBridge {
}
}
#[init(ignore_state)]
#[payable]
pub fn migrate() -> Self {
if env::attached_deposit() != 1 {
env::panic_str("Need money");
}
let old_state: OldPortal = env::state_read().expect("failed");
if env::signer_account_pk() != old_state.owner_pk {
env::panic_str("CannotCallMigrate");
}
env::log_str(&format!("token-bridge/{}#{}: migrate", file!(), line!(),));
Self {
booted: old_state.booted,
core: old_state.core,
gov_idx: 0,
dups: LookupMap::new(b"d".to_vec()),
owner_pk: old_state.owner_pk,
emitter_registration: old_state.emitter_registration,
last_asset: old_state.last_asset,
upgrade_hash: old_state.upgrade_hash,
tokens: old_state.tokens,
key_map: old_state.key_map,
hash_map: old_state.hash_map,
bank: old_state.bank,
}
}
// #[init(ignore_state)]
// #[payable]
// #[private]
// pub fn migrate() -> Self {
// if env::attached_deposit() != 1 {
// env::panic_str("Need money");
// }
// let old_state: OldPortal = env::state_read().expect("failed");
// env::log_str(&format!("token-bridge/{}#{}: migrate", file!(), line!(),));
// Self {
// booted: old_state.booted,
// core: old_state.core,
// gov_idx: 0,
// dups: LookupMap::new(b"d".to_vec()),
// owner_pk: old_state.owner_pk,
// emitter_registration: old_state.emitter_registration,
// last_asset: old_state.last_asset,
// upgrade_hash: old_state.upgrade_hash,
// tokens: old_state.tokens,
// key_map: old_state.key_map,
// hash_map: old_state.hash_map,
// bank: old_state.bank,
// }
// }
}
// let result = await userAccount.functionCall({

View File

@ -601,30 +601,28 @@ impl Wormhole {
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,
}
}
// #[init(ignore_state)]
// #[payable]
// #[private]
// 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");
// 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({

144
near/mainnet_deploy.ts Normal file
View File

@ -0,0 +1,144 @@
// 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";
const { parseSeedPhrase, generateSeedPhrase } = require("near-seed-phrase");
function getConfig(env: any) {
switch (env) {
case "mainnet":
return {
networkId: "mainnet",
nodeUrl: "https://rpc.mainnet.near.org",
wormholeMasterAccount: "wormhole_crypto.near",
wormholeAccount: "contract.wormhole_crypto.near",
portalMasterAccount: "portalbridge.near",
portalAccount: "contract.portalbridge.near",
};
}
return {};
}
async function initNear() {
let config = getConfig("mainnet");
let wormholeKeys = parseSeedPhrase(process.env.WORMHOLE_KEYS);
let portalKeys = parseSeedPhrase(process.env.PORTAL_KEYS);
let wormholeMasterKey = nearAPI.utils.KeyPair.fromString(
wormholeKeys["secretKey"]
);
let portalMasterKey = nearAPI.utils.KeyPair.fromString(
portalKeys["secretKey"]
);
let keyStore = new nearAPI.keyStores.InMemoryKeyStore();
keyStore.setKey(
config.networkId,
config.wormholeMasterAccount,
wormholeMasterKey
);
keyStore.setKey(config.networkId, config.wormholeAccount, wormholeMasterKey);
keyStore.setKey(
config.networkId,
config.portalMasterAccount,
portalMasterKey
);
keyStore.setKey(config.networkId, config.portalAccount, portalMasterKey);
let near = await nearAPI.connect({
deps: {
keyStore,
},
networkId: config.networkId,
nodeUrl: config.nodeUrl,
});
let wormholeMasterAccount = new nearAPI.Account(
near.connection,
config.wormholeMasterAccount
);
let portalMasterAccount = new nearAPI.Account(
near.connection,
config.portalMasterAccount
);
console.log(
"wormhole account: " +
JSON.stringify(await wormholeMasterAccount.getAccountBalance())
);
console.log(
"portal account: " +
JSON.stringify(await portalMasterAccount.getAccountBalance())
);
const wormholeContract = await fs.readFileSync(
"contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm"
);
const portalContract = await fs.readFileSync(
"contracts/token-bridge/target/wasm32-unknown-unknown/release/near_token_bridge.wasm"
);
console.log("setting key for new wormhole contract");
keyStore.setKey(config.networkId, config.wormholeAccount, wormholeMasterKey);
keyStore.setKey(config.networkId, config.portalAccount, portalMasterKey);
console.log("Deploying core/wormhole contract: " + config.wormholeAccount);
let wormholeAccount = await wormholeMasterAccount.createAndDeployContract(
config.wormholeAccount,
wormholeMasterKey.getPublicKey(),
wormholeContract,
new BN("5000000000000000000000000")
);
console.log("Deploying core/portal contract: " + config.portalAccount);
let portalAccount = await portalMasterAccount.createAndDeployContract(
config.portalAccount,
portalMasterKey.getPublicKey(),
portalContract,
new BN("12000000000000000000000000")
);
let result = await wormholeMasterAccount.functionCall({
contractId: config.wormholeAccount,
methodName: "boot_wormhole",
args: {
gset: 0,
addresses: ["58CC3AE5C097b213cE3c81979e1B9f9570746AA5"],
},
gas: 100000000000000,
});
result = await portalMasterAccount.functionCall({
contractId: config.portalAccount,
methodName: "boot_portal",
args: {
core: config.wormholeAccount,
},
gas: 100000000000000,
});
await wormholeMasterAccount.functionCall({
contractId: config.wormholeAccount,
methodName: "register_emitter",
args: { emitter: config.portalAccount },
attachedDeposit: new BN("30000000000000000000000"),
gas: new BN("100000000000000"),
});
console.log("deleting the master key from the token contract");
await portalAccount.deleteKey(portalMasterKey.getPublicKey());
console.log("deleting the master key from the wormhole contract");
await wormholeAccount.deleteKey(wormholeMasterKey.getPublicKey());
}
initNear();

View File

@ -1,4 +1,4 @@
[toolchain]
channel = "nightly"
channel = "1.63"
targets = [ "wasm32-unknown-unknown" ]
profile = "default"

View File

@ -4,6 +4,14 @@ const bs58 = require("bs58");
const fs = require("fs");
const fetch = require("node-fetch");
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
const { createHash } = require('crypto');
import { TestLib } from "./testlib";
import {
ChainId,
CHAIN_ID_NEAR,
} from "@certusone/wormhole-sdk/lib/cjs/utils";
function getConfig(env: any) {
switch (env) {
@ -22,6 +30,10 @@ function getConfig(env: any) {
return {};
}
function hash(string: any) {
return createHash('sha256').update(string).digest('hex');
}
async function testDeploy() {
let config = getConfig(process.env.NEAR_ENV || "sandbox");
@ -56,7 +68,6 @@ async function testDeploy() {
);
let userKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
keyStore.setKey(config.networkId, config.userAccount, userKey);
console.log(
"creating a user account: " +
@ -70,43 +81,92 @@ async function testDeploy() {
userKey.getPublicKey(),
new BN(10).pow(new BN(27))
);
// A whole new world...
keyStore = new nearAPI.keyStores.InMemoryKeyStore();
keyStore.setKey(config.networkId, config.userAccount, userKey);
near = await nearAPI.connect({
deps: {
keyStore,
},
networkId: config.networkId,
nodeUrl: config.nodeUrl,
});
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"));
let h = hash(wormholeContract);
keyStore.setKey(config.networkId, config.wormholeAccount, masterKey);
let ts = new TestLib();
let seq = 1;
let vaa = ts.genCoreUpdate(ts.singleGuardianPrivKey, 0, 0, seq, CHAIN_ID_NEAR, h);
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("submitting vaa");
let result = await userAccount.functionCall({
contractId: config.wormholeAccount,
methodName: "submit_vaa",
args: { vaa: vaa },
attachedDeposit: "12500000000000000000000",
gas: new BN("150000000000000"),
});
console.log("calling upgradeContract");
let result = await userAccount.functionCall({
result = await userAccount.functionCall({
contractId: config.wormholeAccount,
methodName: "update_contract",
args: wormholeContract,
attachedDeposit: "12500000000000000000000",
attachedDeposit: "5279790000000000000000000",
gas: 300000000000000,
});
console.log("done");
console.log(result);
const tokenContract = await fs.readFileSync(
"../contracts/token-bridge/target/wasm32-unknown-unknown/release/near_token_bridge.wasm"
);
h = hash(tokenContract);
seq = seq + 1
vaa = ts.genTokenUpdate(ts.singleGuardianPrivKey, 0, 0, seq, CHAIN_ID_NEAR, h);
console.log("submitting vaa");
result = await userAccount.functionCall({
contractId: config.tokenAccount,
methodName: "submit_vaa",
args: { vaa: vaa },
attachedDeposit: "12500000000000000000000",
gas: new BN("150000000000000"),
});
console.log("submitting vaa again");
result = await userAccount.functionCall({
contractId: config.tokenAccount,
methodName: "submit_vaa",
args: { vaa: vaa },
attachedDeposit: "12500000000000000000000",
gas: new BN("150000000000000"),
});
console.log("calling upgradeContract on the token bridge");
result = await userAccount.functionCall({
contractId: config.tokenAccount,
methodName: "update_contract",
args: tokenContract,
attachedDeposit: "22797900000000000000000000",
gas: 300000000000000,
});
}
testDeploy();

View File

@ -247,6 +247,87 @@ export class TestLib {
);
}
genCoreUpdate(
signers: any,
guardianSet: number,
nonce: number,
seq: number,
tchain: number,
hash: 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", 1),
this.encoder("uint16", tchain),
hash
];
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("")
);
}
genTokenUpdate(
signers: any,
guardianSet: number,
nonce: number,
seq: number,
tchain: number,
hash: string
) {
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", 2),
this.encoder("uint16", tchain),
hash
];
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";