Skip to content

Commit d4563e3

Browse files
authoredJul 29, 2024··
sdk: add missing admin functionality and queries (#466)
1 parent c88f2ed commit d4563e3

File tree

5 files changed

+614
-54
lines changed

5 files changed

+614
-54
lines changed
 

‎evm/ts/src/ntt.ts

+231-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
Chain,
33
Network,
4+
encoding,
45
nativeChainIds,
56
toChainId,
67
} from "@wormhole-foundation/sdk-base";
@@ -9,10 +10,12 @@ import {
910
ChainAddress,
1011
ChainsConfig,
1112
Contracts,
13+
canonicalAddress,
1214
serialize,
15+
toUniversal,
1316
universalAddress,
1417
} from "@wormhole-foundation/sdk-definitions";
15-
import type { EvmChains, EvmPlatformType } from "@wormhole-foundation/sdk-evm";
18+
import type { AnyEvmAddress, EvmChains, EvmPlatformType } from "@wormhole-foundation/sdk-evm";
1619
import {
1720
EvmAddress,
1821
EvmPlatform,
@@ -36,8 +39,7 @@ import {
3639
} from "./bindings.js";
3740

3841
export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
39-
implements NttTransceiver<N, C, WormholeNttTransceiver.VAA>
40-
{
42+
implements NttTransceiver<N, C, WormholeNttTransceiver.VAA> {
4143
transceiver: NttTransceiverBindings.NttTransceiver;
4244
constructor(
4345
readonly manager: EvmNtt<N, C>,
@@ -50,17 +52,59 @@ export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
5052
);
5153
}
5254

55+
getAddress(): ChainAddress<C> {
56+
return { chain: this.manager.chain, address: toUniversal(this.manager.chain, this.address) };
57+
}
58+
5359
encodeFlags(flags: { skipRelay: boolean }): Uint8Array {
5460
return new Uint8Array([flags.skipRelay ? 1 : 0]);
5561
}
5662

57-
async *setPeer(peer: ChainAddress<C>) {
63+
async *setPeer<P extends Chain>(peer: ChainAddress<P>): AsyncGenerator<EvmUnsignedTransaction<N, C>> {
5864
const tx = await this.transceiver.setWormholePeer.populateTransaction(
5965
toChainId(peer.chain),
6066
universalAddress(peer)
6167
);
6268
yield this.manager.createUnsignedTx(tx, "WormholeTransceiver.registerPeer");
6369
}
70+
71+
async getPauser(): Promise<AccountAddress<C> | null> {
72+
const pauser = await this.transceiver.pauser();
73+
return new EvmAddress(pauser) as AccountAddress<C>;
74+
}
75+
76+
async *setPauser(pauser: AccountAddress<C>) {
77+
const canonicalPauser = canonicalAddress({chain: this.manager.chain, address: pauser});
78+
const tx = await this.transceiver.transferPauserCapability.populateTransaction(canonicalPauser);
79+
yield this.manager.createUnsignedTx(tx, "WormholeTransceiver.setPauser");
80+
}
81+
82+
async getPeer<C extends Chain>(chain: C): Promise<ChainAddress<C> | null> {
83+
const peer = await this.transceiver.getWormholePeer(toChainId(chain));
84+
const peerAddress = encoding.hex.decode(peer);
85+
const zeroAddress = new Uint8Array(32);
86+
if (encoding.bytes.equals(zeroAddress, peerAddress)) {
87+
return null;
88+
}
89+
90+
return {
91+
chain: chain,
92+
address: toUniversal(chain, peerAddress),
93+
};
94+
}
95+
96+
async isEvmChain(chain: Chain): Promise<boolean> {
97+
return await this.transceiver.isWormholeEvmChain(toChainId(chain));
98+
}
99+
100+
async *setIsEvmChain(chain: Chain, isEvm: boolean) {
101+
const tx = await this.transceiver.setIsWormholeEvmChain.populateTransaction(
102+
toChainId(chain),
103+
isEvm
104+
);
105+
yield this.manager.createUnsignedTx(tx, "WormholeTransceiver.setIsEvmChain");
106+
}
107+
64108
async *receive(attestation: WormholeNttTransceiver.VAA) {
65109
const tx = await this.transceiver.receiveMessage.populateTransaction(
66110
serialize(attestation)
@@ -77,6 +121,17 @@ export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
77121
);
78122
}
79123

124+
async *setIsWormholeRelayingEnabled(destChain: Chain, enabled: boolean) {
125+
const tx = await this.transceiver.setIsWormholeRelayingEnabled.populateTransaction(
126+
toChainId(destChain),
127+
enabled
128+
);
129+
yield this.manager.createUnsignedTx(
130+
tx,
131+
"WormholeTransceiver.setWormholeRelayingEnabled"
132+
);
133+
}
134+
80135
async isSpecialRelayingEnabled(destChain: Chain): Promise<boolean> {
81136
return await this.transceiver.isSpecialRelayingEnabled(
82137
toChainId(destChain)
@@ -85,8 +140,7 @@ export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
85140
}
86141

87142
export class EvmNtt<N extends Network, C extends EvmChains>
88-
implements Ntt<N, C>
89-
{
143+
implements Ntt<N, C> {
90144
tokenAddress: string;
91145
readonly chainId: bigint;
92146
manager: NttManagerBindings.NttManager;
@@ -117,14 +171,66 @@ export class EvmNtt<N extends Network, C extends EvmChains>
117171
this.provider
118172
);
119173

120-
this.xcvrs = [
121-
// Enable more Transceivers here
122-
new EvmNttWormholeTranceiver(
123-
this,
124-
contracts.ntt.transceiver.wormhole!,
125-
abiBindings!
126-
),
127-
];
174+
if (contracts.ntt.transceiver.wormhole != null) {
175+
this.xcvrs = [
176+
// Enable more Transceivers here
177+
new EvmNttWormholeTranceiver(
178+
this,
179+
contracts.ntt.transceiver.wormhole,
180+
abiBindings!
181+
),
182+
];
183+
} else {
184+
this.xcvrs = [];
185+
}
186+
}
187+
188+
async getTransceiver(ix: number): Promise<NttTransceiver<N, C, any> | null> {
189+
// TODO: should we make an RPC call here, or just trust that the xcvrs are set up correctly?
190+
return this.xcvrs[ix] || null;
191+
}
192+
193+
async getMode(): Promise<Ntt.Mode> {
194+
const mode: bigint = await this.manager.getMode();
195+
return mode === 0n ? "locking" : "burning";
196+
}
197+
198+
async isPaused(): Promise<boolean> {
199+
return await this.manager.isPaused();
200+
}
201+
202+
async *pause() {
203+
const tx = await this.manager.pause.populateTransaction()
204+
yield this.createUnsignedTx(tx, "Ntt.pause");
205+
}
206+
207+
async *unpause() {
208+
const tx = await this.manager.unpause.populateTransaction()
209+
yield this.createUnsignedTx(tx, "Ntt.unpause");
210+
}
211+
212+
async getOwner(): Promise<AccountAddress<C>> {
213+
return new EvmAddress(await this.manager.owner()) as AccountAddress<C>;
214+
}
215+
216+
async getPauser(): Promise<AccountAddress<C> | null> {
217+
return new EvmAddress(await this.manager.pauser()) as AccountAddress<C>;
218+
}
219+
220+
async *setOwner(owner: AnyEvmAddress) {
221+
const canonicalOwner = new EvmAddress(owner).toString();
222+
const tx = await this.manager.transferOwnership.populateTransaction(canonicalOwner);
223+
yield this.createUnsignedTx(tx, "Ntt.setOwner");
224+
}
225+
226+
async *setPauser(pauser: AnyEvmAddress) {
227+
const canonicalPauser = new EvmAddress(pauser).toString();
228+
const tx = await this.manager.transferPauserCapability.populateTransaction(canonicalPauser);
229+
yield this.createUnsignedTx(tx, "Ntt.setPauser");
230+
}
231+
232+
async getThreshold(): Promise<number> {
233+
return Number(await this.manager.getThreshold());
128234
}
129235

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

296+
async getPeer<C extends Chain>(chain: C): Promise<Ntt.Peer<C> | null> {
297+
const peer = await this.manager.getPeer(toChainId(chain));
298+
const peerAddress = encoding.hex.decode(peer.peerAddress);
299+
const zeroAddress = new Uint8Array(32);
300+
if (encoding.bytes.equals(zeroAddress, peerAddress)) {
301+
return null;
302+
}
303+
304+
return {
305+
address: { chain: chain, address: toUniversal(chain, peerAddress) },
306+
tokenDecimals: Number(peer.tokenDecimals),
307+
inboundLimit: await this.getInboundLimit(chain),
308+
};
309+
}
310+
190311
static async fromRpc<N extends Network>(
191312
provider: Provider,
192313
config: ChainsConfig<N, EvmPlatformType>
@@ -342,10 +463,39 @@ export class EvmNtt<N extends Network, C extends EvmChains>
342463
return await this.manager.getCurrentOutboundCapacity();
343464
}
344465

466+
async getOutboundLimit(): Promise<bigint> {
467+
const encoded: EncodedTrimmedAmount = (await this.manager.getOutboundLimitParams()).limit;
468+
const trimmedAmount: TrimmedAmount = decodeTrimmedAmount(encoded);
469+
const tokenDecimals = await this.getTokenDecimals();
470+
471+
return untrim(trimmedAmount, tokenDecimals);
472+
}
473+
474+
async *setOutboundLimit(limit: bigint) {
475+
const tx = await this.manager.setOutboundLimit.populateTransaction(limit);
476+
yield this.createUnsignedTx(tx, "Ntt.setOutboundLimit");
477+
}
478+
345479
async getCurrentInboundCapacity(fromChain: Chain): Promise<bigint> {
346480
return await this.manager.getCurrentInboundCapacity(toChainId(fromChain));
347481
}
348482

483+
async getInboundLimit(fromChain: Chain): Promise<bigint> {
484+
const encoded: EncodedTrimmedAmount = (await this.manager.getInboundLimitParams(toChainId(fromChain))).limit;
485+
const trimmedAmount: TrimmedAmount = decodeTrimmedAmount(encoded);
486+
const tokenDecimals = await this.getTokenDecimals();
487+
488+
return untrim(trimmedAmount, tokenDecimals);
489+
}
490+
491+
async *setInboundLimit(fromChain: Chain, limit: bigint) {
492+
const tx = await this.manager.setInboundLimit.populateTransaction(
493+
limit,
494+
toChainId(fromChain)
495+
);
496+
yield this.createUnsignedTx(tx, "Ntt.setInboundLimit");
497+
}
498+
349499
async getRateLimitDuration(): Promise<bigint> {
350500
return await this.manager.rateLimitDuration();
351501
}
@@ -381,6 +531,40 @@ export class EvmNtt<N extends Network, C extends EvmChains>
381531
yield this.createUnsignedTx(tx, "Ntt.completeInboundQueuedTransfer");
382532
}
383533

534+
async verifyAddresses(): Promise<Partial<Ntt.Contracts> | null> {
535+
const local: Partial<Ntt.Contracts> = {
536+
manager: this.managerAddress,
537+
token: this.tokenAddress,
538+
transceiver: {
539+
wormhole: this.xcvrs[0]?.address,
540+
},
541+
// TODO: what about the quoter?
542+
};
543+
544+
const remote: Partial<Ntt.Contracts> = {
545+
manager: this.managerAddress,
546+
token: await this.manager.token(),
547+
transceiver: {
548+
wormhole: (await this.manager.getTransceivers())[0]! // TODO: make this more generic
549+
},
550+
};
551+
552+
const deleteMatching = (a: any, b: any) => {
553+
for (const k in a) {
554+
if (typeof a[k] === "object") {
555+
deleteMatching(a[k], b[k]);
556+
if (Object.keys(a[k]).length === 0) delete a[k];
557+
} else if (a[k] === b[k]) {
558+
delete a[k];
559+
}
560+
}
561+
}
562+
563+
deleteMatching(remote, local);
564+
565+
return Object.keys(remote).length > 0 ? remote : null;
566+
}
567+
384568
createUnsignedTx(
385569
txReq: TransactionRequest,
386570
description: string,
@@ -395,3 +579,36 @@ export class EvmNtt<N extends Network, C extends EvmChains>
395579
);
396580
}
397581
}
582+
583+
type EncodedTrimmedAmount = bigint; // uint72
584+
585+
type TrimmedAmount = {
586+
amount: bigint;
587+
decimals: number;
588+
};
589+
590+
function decodeTrimmedAmount(encoded: EncodedTrimmedAmount): TrimmedAmount {
591+
const decimals = Number(encoded & 0xffn);
592+
const amount = encoded >> 8n;
593+
return {
594+
amount,
595+
decimals,
596+
};
597+
}
598+
599+
function untrim(trimmed: TrimmedAmount, toDecimals: number): bigint {
600+
const { amount, decimals: fromDecimals } = trimmed;
601+
return scale(amount, fromDecimals, toDecimals);
602+
}
603+
604+
function scale(amount: bigint, fromDecimals: number, toDecimals: number): bigint {
605+
if (fromDecimals == toDecimals) {
606+
return amount;
607+
}
608+
609+
if (fromDecimals > toDecimals) {
610+
return amount / (10n ** BigInt(fromDecimals - toDecimals));
611+
} else {
612+
return amount * (10n ** BigInt(toDecimals - fromDecimals));
613+
}
614+
}

‎sdk/__tests__/utils.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export async function link(chainInfos: Ctx[]) {
127127
chain: hubChain,
128128
emitter: Wormhole.chainAddress(
129129
hubChain,
130-
hub.contracts!.transceiver.wormhole
130+
hub.contracts!.transceiver.wormhole!
131131
).address.toUniversalAddress(),
132132
sequence: 0n,
133133
};
@@ -595,7 +595,7 @@ async function setupPeer(targetCtx: Ctx, peerCtx: Ctx) {
595595
} = peerCtx.contracts!;
596596

597597
const peerManager = Wormhole.chainAddress(peer.chain, manager);
598-
const peerTransceiver = Wormhole.chainAddress(peer.chain, transceiver);
598+
const peerTransceiver = Wormhole.chainAddress(peer.chain, transceiver!);
599599

600600
const tokenDecimals = target.config.nativeTokenDecimals;
601601
const inboundLimit = amount.units(amount.parse("1000", tokenDecimals));
@@ -625,7 +625,7 @@ async function setupPeer(targetCtx: Ctx, peerCtx: Ctx) {
625625
) {
626626
const nativeSigner = (signer as NativeSigner).unwrap();
627627
const xcvr = WormholeTransceiver__factory.connect(
628-
targetCtx.contracts!.transceiver.wormhole,
628+
targetCtx.contracts!.transceiver.wormhole!,
629629
nativeSigner.signer
630630
);
631631
const peerChainId = toChainId(peer.chain);

0 commit comments

Comments
 (0)
Please sign in to comment.