Skip to content

Commit

Permalink
sdk: add missing admin functionality and queries (#466)
Browse files Browse the repository at this point in the history
  • Loading branch information
kcsongor authored Jul 29, 2024
1 parent c88f2ed commit d4563e3
Show file tree
Hide file tree
Showing 5 changed files with 614 additions and 54 deletions.
245 changes: 231 additions & 14 deletions evm/ts/src/ntt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Chain,
Network,
encoding,
nativeChainIds,
toChainId,
} from "@wormhole-foundation/sdk-base";
Expand All @@ -9,10 +10,12 @@ import {
ChainAddress,
ChainsConfig,
Contracts,
canonicalAddress,
serialize,
toUniversal,
universalAddress,
} from "@wormhole-foundation/sdk-definitions";
import type { EvmChains, EvmPlatformType } from "@wormhole-foundation/sdk-evm";
import type { AnyEvmAddress, EvmChains, EvmPlatformType } from "@wormhole-foundation/sdk-evm";
import {
EvmAddress,
EvmPlatform,
Expand All @@ -36,8 +39,7 @@ import {
} from "./bindings.js";

export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
implements NttTransceiver<N, C, WormholeNttTransceiver.VAA>
{
implements NttTransceiver<N, C, WormholeNttTransceiver.VAA> {
transceiver: NttTransceiverBindings.NttTransceiver;
constructor(
readonly manager: EvmNtt<N, C>,
Expand All @@ -50,17 +52,59 @@ export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
);
}

getAddress(): ChainAddress<C> {
return { chain: this.manager.chain, address: toUniversal(this.manager.chain, this.address) };
}

encodeFlags(flags: { skipRelay: boolean }): Uint8Array {
return new Uint8Array([flags.skipRelay ? 1 : 0]);
}

async *setPeer(peer: ChainAddress<C>) {
async *setPeer<P extends Chain>(peer: ChainAddress<P>): AsyncGenerator<EvmUnsignedTransaction<N, C>> {
const tx = await this.transceiver.setWormholePeer.populateTransaction(
toChainId(peer.chain),
universalAddress(peer)
);
yield this.manager.createUnsignedTx(tx, "WormholeTransceiver.registerPeer");
}

async getPauser(): Promise<AccountAddress<C> | null> {
const pauser = await this.transceiver.pauser();
return new EvmAddress(pauser) as AccountAddress<C>;
}

async *setPauser(pauser: AccountAddress<C>) {
const canonicalPauser = canonicalAddress({chain: this.manager.chain, address: pauser});
const tx = await this.transceiver.transferPauserCapability.populateTransaction(canonicalPauser);
yield this.manager.createUnsignedTx(tx, "WormholeTransceiver.setPauser");
}

async getPeer<C extends Chain>(chain: C): Promise<ChainAddress<C> | null> {
const peer = await this.transceiver.getWormholePeer(toChainId(chain));
const peerAddress = encoding.hex.decode(peer);
const zeroAddress = new Uint8Array(32);
if (encoding.bytes.equals(zeroAddress, peerAddress)) {
return null;
}

return {
chain: chain,
address: toUniversal(chain, peerAddress),
};
}

async isEvmChain(chain: Chain): Promise<boolean> {
return await this.transceiver.isWormholeEvmChain(toChainId(chain));
}

async *setIsEvmChain(chain: Chain, isEvm: boolean) {
const tx = await this.transceiver.setIsWormholeEvmChain.populateTransaction(
toChainId(chain),
isEvm
);
yield this.manager.createUnsignedTx(tx, "WormholeTransceiver.setIsEvmChain");
}

async *receive(attestation: WormholeNttTransceiver.VAA) {
const tx = await this.transceiver.receiveMessage.populateTransaction(
serialize(attestation)
Expand All @@ -77,6 +121,17 @@ export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
);
}

async *setIsWormholeRelayingEnabled(destChain: Chain, enabled: boolean) {
const tx = await this.transceiver.setIsWormholeRelayingEnabled.populateTransaction(
toChainId(destChain),
enabled
);
yield this.manager.createUnsignedTx(
tx,
"WormholeTransceiver.setWormholeRelayingEnabled"
);
}

async isSpecialRelayingEnabled(destChain: Chain): Promise<boolean> {
return await this.transceiver.isSpecialRelayingEnabled(
toChainId(destChain)
Expand All @@ -85,8 +140,7 @@ export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
}

export class EvmNtt<N extends Network, C extends EvmChains>
implements Ntt<N, C>
{
implements Ntt<N, C> {
tokenAddress: string;
readonly chainId: bigint;
manager: NttManagerBindings.NttManager;
Expand Down Expand Up @@ -117,14 +171,66 @@ export class EvmNtt<N extends Network, C extends EvmChains>
this.provider
);

this.xcvrs = [
// Enable more Transceivers here
new EvmNttWormholeTranceiver(
this,
contracts.ntt.transceiver.wormhole!,
abiBindings!
),
];
if (contracts.ntt.transceiver.wormhole != null) {
this.xcvrs = [
// Enable more Transceivers here
new EvmNttWormholeTranceiver(
this,
contracts.ntt.transceiver.wormhole,
abiBindings!
),
];
} else {
this.xcvrs = [];
}
}

async getTransceiver(ix: number): Promise<NttTransceiver<N, C, any> | null> {
// TODO: should we make an RPC call here, or just trust that the xcvrs are set up correctly?
return this.xcvrs[ix] || null;
}

async getMode(): Promise<Ntt.Mode> {
const mode: bigint = await this.manager.getMode();
return mode === 0n ? "locking" : "burning";
}

async isPaused(): Promise<boolean> {
return await this.manager.isPaused();
}

async *pause() {
const tx = await this.manager.pause.populateTransaction()
yield this.createUnsignedTx(tx, "Ntt.pause");
}

async *unpause() {
const tx = await this.manager.unpause.populateTransaction()
yield this.createUnsignedTx(tx, "Ntt.unpause");
}

async getOwner(): Promise<AccountAddress<C>> {
return new EvmAddress(await this.manager.owner()) as AccountAddress<C>;
}

async getPauser(): Promise<AccountAddress<C> | null> {
return new EvmAddress(await this.manager.pauser()) as AccountAddress<C>;
}

async *setOwner(owner: AnyEvmAddress) {
const canonicalOwner = new EvmAddress(owner).toString();
const tx = await this.manager.transferOwnership.populateTransaction(canonicalOwner);
yield this.createUnsignedTx(tx, "Ntt.setOwner");
}

async *setPauser(pauser: AnyEvmAddress) {
const canonicalPauser = new EvmAddress(pauser).toString();
const tx = await this.manager.transferPauserCapability.populateTransaction(canonicalPauser);
yield this.createUnsignedTx(tx, "Ntt.setPauser");
}

async getThreshold(): Promise<number> {
return Number(await this.manager.getThreshold());
}

async isRelayingAvailable(destination: Chain): Promise<boolean> {
Expand Down Expand Up @@ -187,6 +293,21 @@ export class EvmNtt<N extends Network, C extends EvmChains>
);
}

async getPeer<C extends Chain>(chain: C): Promise<Ntt.Peer<C> | null> {
const peer = await this.manager.getPeer(toChainId(chain));
const peerAddress = encoding.hex.decode(peer.peerAddress);
const zeroAddress = new Uint8Array(32);
if (encoding.bytes.equals(zeroAddress, peerAddress)) {
return null;
}

return {
address: { chain: chain, address: toUniversal(chain, peerAddress) },
tokenDecimals: Number(peer.tokenDecimals),
inboundLimit: await this.getInboundLimit(chain),
};
}

static async fromRpc<N extends Network>(
provider: Provider,
config: ChainsConfig<N, EvmPlatformType>
Expand Down Expand Up @@ -342,10 +463,39 @@ export class EvmNtt<N extends Network, C extends EvmChains>
return await this.manager.getCurrentOutboundCapacity();
}

async getOutboundLimit(): Promise<bigint> {
const encoded: EncodedTrimmedAmount = (await this.manager.getOutboundLimitParams()).limit;
const trimmedAmount: TrimmedAmount = decodeTrimmedAmount(encoded);
const tokenDecimals = await this.getTokenDecimals();

return untrim(trimmedAmount, tokenDecimals);
}

async *setOutboundLimit(limit: bigint) {
const tx = await this.manager.setOutboundLimit.populateTransaction(limit);
yield this.createUnsignedTx(tx, "Ntt.setOutboundLimit");
}

async getCurrentInboundCapacity(fromChain: Chain): Promise<bigint> {
return await this.manager.getCurrentInboundCapacity(toChainId(fromChain));
}

async getInboundLimit(fromChain: Chain): Promise<bigint> {
const encoded: EncodedTrimmedAmount = (await this.manager.getInboundLimitParams(toChainId(fromChain))).limit;
const trimmedAmount: TrimmedAmount = decodeTrimmedAmount(encoded);
const tokenDecimals = await this.getTokenDecimals();

return untrim(trimmedAmount, tokenDecimals);
}

async *setInboundLimit(fromChain: Chain, limit: bigint) {
const tx = await this.manager.setInboundLimit.populateTransaction(
limit,
toChainId(fromChain)
);
yield this.createUnsignedTx(tx, "Ntt.setInboundLimit");
}

async getRateLimitDuration(): Promise<bigint> {
return await this.manager.rateLimitDuration();
}
Expand Down Expand Up @@ -381,6 +531,40 @@ export class EvmNtt<N extends Network, C extends EvmChains>
yield this.createUnsignedTx(tx, "Ntt.completeInboundQueuedTransfer");
}

async verifyAddresses(): Promise<Partial<Ntt.Contracts> | null> {
const local: Partial<Ntt.Contracts> = {
manager: this.managerAddress,
token: this.tokenAddress,
transceiver: {
wormhole: this.xcvrs[0]?.address,
},
// TODO: what about the quoter?
};

const remote: Partial<Ntt.Contracts> = {
manager: this.managerAddress,
token: await this.manager.token(),
transceiver: {
wormhole: (await this.manager.getTransceivers())[0]! // TODO: make this more generic
},
};

const deleteMatching = (a: any, b: any) => {
for (const k in a) {
if (typeof a[k] === "object") {
deleteMatching(a[k], b[k]);
if (Object.keys(a[k]).length === 0) delete a[k];
} else if (a[k] === b[k]) {
delete a[k];
}
}
}

deleteMatching(remote, local);

return Object.keys(remote).length > 0 ? remote : null;
}

createUnsignedTx(
txReq: TransactionRequest,
description: string,
Expand All @@ -395,3 +579,36 @@ export class EvmNtt<N extends Network, C extends EvmChains>
);
}
}

type EncodedTrimmedAmount = bigint; // uint72

type TrimmedAmount = {
amount: bigint;
decimals: number;
};

function decodeTrimmedAmount(encoded: EncodedTrimmedAmount): TrimmedAmount {
const decimals = Number(encoded & 0xffn);
const amount = encoded >> 8n;
return {
amount,
decimals,
};
}

function untrim(trimmed: TrimmedAmount, toDecimals: number): bigint {
const { amount, decimals: fromDecimals } = trimmed;
return scale(amount, fromDecimals, toDecimals);
}

function scale(amount: bigint, fromDecimals: number, toDecimals: number): bigint {
if (fromDecimals == toDecimals) {
return amount;
}

if (fromDecimals > toDecimals) {
return amount / (10n ** BigInt(fromDecimals - toDecimals));
} else {
return amount * (10n ** BigInt(toDecimals - fromDecimals));
}
}
6 changes: 3 additions & 3 deletions sdk/__tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export async function link(chainInfos: Ctx[]) {
chain: hubChain,
emitter: Wormhole.chainAddress(
hubChain,
hub.contracts!.transceiver.wormhole
hub.contracts!.transceiver.wormhole!
).address.toUniversalAddress(),
sequence: 0n,
};
Expand Down Expand Up @@ -595,7 +595,7 @@ async function setupPeer(targetCtx: Ctx, peerCtx: Ctx) {
} = peerCtx.contracts!;

const peerManager = Wormhole.chainAddress(peer.chain, manager);
const peerTransceiver = Wormhole.chainAddress(peer.chain, transceiver);
const peerTransceiver = Wormhole.chainAddress(peer.chain, transceiver!);

const tokenDecimals = target.config.nativeTokenDecimals;
const inboundLimit = amount.units(amount.parse("1000", tokenDecimals));
Expand Down Expand Up @@ -625,7 +625,7 @@ async function setupPeer(targetCtx: Ctx, peerCtx: Ctx) {
) {
const nativeSigner = (signer as NativeSigner).unwrap();
const xcvr = WormholeTransceiver__factory.connect(
targetCtx.contracts!.transceiver.wormhole,
targetCtx.contracts!.transceiver.wormhole!,
nativeSigner.signer
);
const peerChainId = toChainId(peer.chain);
Expand Down
Loading

0 comments on commit d4563e3

Please sign in to comment.