Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDK: Add Protocol definition, add Solana implementation #181

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1111188
Start to add protocol definition
barnjamin Jun 18, 2024
e6fbd9b
replace helper function to use protocol from sdk
barnjamin Jun 18, 2024
539b7d5
replace postVaa with method that takes signer and no guardian args
barnjamin Jun 19, 2024
a9d4a2f
replacing more ix with tx
barnjamin Jun 19, 2024
4b8e21a
replace more execute fns
barnjamin Jun 19, 2024
6c2d364
re-enable a test
barnjamin Jun 19, 2024
0b9c764
replace sdk signer with subclass && provide unwrap to get keypair, up…
barnjamin Jun 20, 2024
200f5bd
settle order in test
barnjamin Jun 20, 2024
f3b2601
remove unnecessary solana address creation
barnjamin Jun 20, 2024
b8c15da
more port
barnjamin Jun 20, 2024
4c0dfe2
partial settleOrder
barnjamin Jun 20, 2024
2e481c2
Add prepare order ix to settle order
barnjamin Jun 20, 2024
ac625b5
no need for blockhash before signer
barnjamin Jun 20, 2024
3026ec7
Refactor how vaas are posted
barnjamin Jun 21, 2024
e6f3d3b
cleanup
barnjamin Jun 21, 2024
c2af1f9
start to add token router
barnjamin Jun 21, 2024
b55c9fb
adding prepare order
barnjamin Jun 21, 2024
13707de
add utils for creating order request
barnjamin Jun 22, 2024
07ca3ba
refactor addresseses
barnjamin Jun 24, 2024
b447009
update spots where we post the vaa to the newer style
barnjamin Jun 24, 2024
11930ba
more address type consolidation
barnjamin Jun 24, 2024
be7f6f3
update testnet fork test
barnjamin Jun 24, 2024
f1bc1a7
more const changes, move types out of index file for matching engine …
barnjamin Jun 25, 2024
a544b5f
re-use existing CCTP serde, actually deserialize created VAA to corre…
barnjamin Jun 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions solana/Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ cluster = "Localnet"
wallet = "ts/tests/keys/pFCBP4bhqdSsrWUVTgqhPsLrfEdChBK17vgFM7TxjxQ.json"

[scripts]
test-local = "npx ts-mocha -p ./tsconfig.anchor.json -t 1000000 --bail --exit ts/tests/0[0-9]*.ts"
test-upgrade-fork = "npx ts-mocha -p ./tsconfig.anchor.json -t 1000000 --bail --exit ts/tests/1[0-9]*.ts"
test-local = "npx ts-mocha -p ./tsconfig.anchor.json -t 1000000 --bail --full-trace --exit ts/tests/0[0-9]*.ts"
test-upgrade-fork = "npx ts-mocha -p ./tsconfig.anchor.json -t 1000000 --bail --full-trace --exit ts/tests/1[0-9]*.ts"

[test]
startup_wait = 20000
Expand Down
41 changes: 5 additions & 36 deletions solana/ts/src/cctp/messageTransmitter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import { IDL, MessageTransmitter } from "../types/message_transmitter";
import { MessageSent } from "./MessageSent";
import { MessageTransmitterConfig } from "./MessageTransmitterConfig";
import { UsedNonses } from "./UsedNonces";

export const PROGRAM_IDS = ["CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd"] as const;

export type ProgramId = (typeof PROGRAM_IDS)[number];
import { CircleContracts } from "@wormhole-foundation/sdk-base/contracts";

export type ReceiveTokenMessengerMinterMessageAccounts = {
authority: PublicKey;
Expand All @@ -28,15 +25,11 @@ export type ReceiveTokenMessengerMinterMessageAccounts = {
};

export class MessageTransmitterProgram {
private _programId: ProgramId;

program: Program<MessageTransmitter>;

constructor(connection: Connection, programId?: ProgramId) {
this._programId = programId ?? testnet();
this.program = new Program(IDL, new PublicKey(this._programId), {
connection,
});
constructor(connection: Connection, private contracts: CircleContracts) {
const programId = new PublicKey(contracts.messageTransmitter);
this.program = new Program(IDL, new PublicKey(programId), { connection });
}

get ID(): PublicKey {
Expand Down Expand Up @@ -71,23 +64,7 @@ export class MessageTransmitterProgram {
}

tokenMessengerMinterProgram(): TokenMessengerMinterProgram {
switch (this._programId) {
case testnet(): {
return new TokenMessengerMinterProgram(
this.program.provider.connection,
"CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3",
);
}
case mainnet(): {
return new TokenMessengerMinterProgram(
this.program.provider.connection,
"CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3",
);
}
default: {
throw new Error("unsupported network");
}
}
return new TokenMessengerMinterProgram(this.program.provider.connection, this.contracts);
}

receiveTokenMessengerMinterMessageAccounts(
Expand Down Expand Up @@ -133,11 +110,3 @@ export class MessageTransmitterProgram {
.instruction();
}
}

export function mainnet(): ProgramId {
return "CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd";
}

export function testnet(): ProgramId {
return "CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd";
}
127 changes: 52 additions & 75 deletions solana/ts/src/cctp/messages.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers } from "ethers";
import { CircleBridge, UniversalAddress } from "@wormhole-foundation/sdk-definitions";

export type Cctp = {
version: number;
Expand All @@ -13,9 +13,9 @@ export type Cctp = {
// Taken from https://developers.circle.com/stablecoins/docs/message-format.
export class CctpMessage {
cctp: Cctp;
message: Buffer;
message: CctpTokenBurnMessage;

constructor(cctp: Cctp, message: Buffer) {
constructor(cctp: Cctp, message: CctpTokenBurnMessage) {
this.cctp = cctp;
this.message = message;
}
Expand All @@ -30,31 +30,60 @@ export class CctpMessage {

static decode(buf: Readonly<Buffer>): CctpMessage {
const version = buf.readUInt32BE(0);
const sourceDomain = buf.readUInt32BE(4);
const destinationDomain = buf.readUInt32BE(8);
const nonce = buf.readBigUInt64BE(12);
const sender = Array.from(buf.slice(20, 52));
const recipient = Array.from(buf.slice(52, 84));
const targetCaller = Array.from(buf.slice(84, 116));
const message = buf.subarray(116);

const [msg] = CircleBridge.deserialize(new Uint8Array(buf));
const {
sourceDomain,
destinationDomain,
nonce,
sender,
recipient,
destinationCaller,
payload,
} = msg;

const { burnToken, mintRecipient, amount, messageSender } = payload;
const header: Cctp = {
version,
sourceDomain,
destinationDomain,
nonce,
sender: Array.from(sender.toUint8Array()),
recipient: Array.from(recipient.toUint8Array()),
targetCaller: Array.from(destinationCaller.toUint8Array()),
};

return new CctpMessage(
{
header,
new CctpTokenBurnMessage(
header,
version,
sourceDomain,
destinationDomain,
nonce,
sender,
recipient,
targetCaller,
},
message,
Array.from(burnToken.toUint8Array()),
Array.from(mintRecipient.toUint8Array()),
amount,
Array.from(messageSender.toUint8Array()),
),
);
}

encode(): Buffer {
const { cctp, message } = this;
return Buffer.concat([encodeCctp(cctp), message]);
return Buffer.from(
CircleBridge.serialize({
sourceDomain: cctp.sourceDomain,
destinationDomain: cctp.destinationDomain,
nonce: cctp.nonce,
sender: new UniversalAddress(new Uint8Array(cctp.sender)),
recipient: new UniversalAddress(new Uint8Array(cctp.recipient)),
destinationCaller: new UniversalAddress(new Uint8Array(cctp.targetCaller)),
payload: {
burnToken: new UniversalAddress(new Uint8Array(message.burnTokenAddress)),
mintRecipient: new UniversalAddress(new Uint8Array(message.mintRecipient)),
amount: message.amount,
messageSender: new UniversalAddress(new Uint8Array(message.sender)),
},
}),
);
}
}

Expand Down Expand Up @@ -91,63 +120,11 @@ export class CctpTokenBurnMessage {
}

static decode(buf: Readonly<Buffer>): CctpTokenBurnMessage {
const { cctp, message } = CctpMessage.decode(buf);
const version = message.readUInt32BE(0);
const burnTokenAddress = Array.from(message.subarray(4, 36));
const mintRecipient = Array.from(message.subarray(36, 68));
const amount = BigInt(ethers.BigNumber.from(message.subarray(68, 100)).toString());
const sender = Array.from(message.subarray(100, 132));

return new CctpTokenBurnMessage(
cctp,
version,
burnTokenAddress,
mintRecipient,
amount,
sender,
);
const { message } = CctpMessage.decode(buf);
return message;
}

encode(): Buffer {
const buf = Buffer.alloc(132);

const { cctp, version, burnTokenAddress, mintRecipient, amount, sender } = this;

let offset = 0;
offset = buf.writeUInt32BE(version, offset);
buf.set(burnTokenAddress, offset);
offset += 32;
buf.set(mintRecipient, offset);
offset += 32;

// Special handling w/ uint256. This value will most likely encoded in < 32 bytes, so we
// jump ahead by 32 and subtract the length of the encoded value.
const encodedAmount = ethers.utils.arrayify(ethers.BigNumber.from(amount.toString()));
buf.set(encodedAmount, (offset += 32) - encodedAmount.length);

buf.set(sender, offset);
offset += 32;

return Buffer.concat([encodeCctp(cctp), buf]);
return new CctpMessage(this.cctp, this).encode();
}
}

function encodeCctp(cctp: Cctp): Buffer {
const buf = Buffer.alloc(116);

const { version, sourceDomain, destinationDomain, nonce, sender, recipient, targetCaller } =
cctp;

let offset = 0;
offset = buf.writeUInt32BE(version, offset);
offset = buf.writeUInt32BE(sourceDomain, offset);
offset = buf.writeUInt32BE(destinationDomain, offset);
offset = buf.writeBigUInt64BE(nonce, offset);
buf.set(sender, offset);
offset += 32;
buf.set(recipient, offset);
offset += 32;
buf.set(targetCaller, offset);

return buf;
}
43 changes: 6 additions & 37 deletions solana/ts/src/cctp/tokenMessengerMinter/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { Program } from "anchor-0.29.0";
import { Connection, PublicKey } from "@solana/web3.js";
import { CircleContracts } from "@wormhole-foundation/sdk-base/contracts";
import { Program } from "anchor-0.29.0";
import { MessageTransmitterProgram } from "../messageTransmitter";
import { IDL, TokenMessengerMinter } from "../types/token_messenger_minter";
import { RemoteTokenMessenger } from "./RemoteTokenMessenger";

export const PROGRAM_IDS = ["CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3"] as const;

export type ProgramId = (typeof PROGRAM_IDS)[number];

export type DepositForBurnWithCallerAccounts = {
senderAuthority: PublicKey;
messageTransmitterConfig: PublicKey;
Expand All @@ -21,15 +18,11 @@ export type DepositForBurnWithCallerAccounts = {
};

export class TokenMessengerMinterProgram {
private _programId: ProgramId;

program: Program<TokenMessengerMinter>;

constructor(connection: Connection, programId?: ProgramId) {
this._programId = programId ?? testnet();
this.program = new Program(IDL, new PublicKey(this._programId), {
connection,
});
constructor(connection: Connection, private contracts: CircleContracts) {
const programId = new PublicKey(contracts.tokenMessenger);
this.program = new Program(IDL, programId, { connection });
}

get ID(): PublicKey {
Expand Down Expand Up @@ -89,23 +82,7 @@ export class TokenMessengerMinterProgram {
}

messageTransmitterProgram(): MessageTransmitterProgram {
switch (this._programId) {
case testnet(): {
return new MessageTransmitterProgram(
this.program.provider.connection,
"CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd",
);
}
case mainnet(): {
return new MessageTransmitterProgram(
this.program.provider.connection,
"CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd",
);
}
default: {
throw new Error("unsupported network");
}
}
return new MessageTransmitterProgram(this.program.provider.connection, this.contracts);
}

depositForBurnWithCallerAccounts(
Expand All @@ -126,11 +103,3 @@ export class TokenMessengerMinterProgram {
};
}
}

export function mainnet(): ProgramId {
return "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3";
}

export function testnet(): ProgramId {
return "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3";
}
41 changes: 3 additions & 38 deletions solana/ts/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
import { MessageTransmitterProgram } from "../cctp";
import { BN } from "@coral-xyz/anchor";
import { VAA, keccak256 } from "@wormhole-foundation/sdk-definitions";

export * from "./messages";
export * from "./state";

export type Uint64 = bigint | BN | number;

export function isUint64(value: Uint64): boolean {
return (
typeof value === "bigint" ||
(typeof value === "object" && value instanceof BN) ||
typeof value === "number"
);
}

export function uint64ToBigInt(value: Uint64): bigint {
if (typeof value === "bigint") {
return value;
Expand All @@ -38,32 +31,7 @@ export function uint64ToBN(value: Uint64): BN {
}

export type VaaHash = Array<number> | Buffer | Uint8Array;

export function vaaHashToUint8Array(vaaHash: VaaHash): Uint8Array {
if (Array.isArray(vaaHash)) {
return Uint8Array.from(vaaHash);
} else if (Buffer.isBuffer(vaaHash)) {
return Uint8Array.from(vaaHash);
} else {
return vaaHash;
}
}

export function vaaHashToBuffer(vaaHash: VaaHash): Buffer {
if (Buffer.isBuffer(vaaHash)) {
return vaaHash;
} else {
return Buffer.from(vaaHashToUint8Array(vaaHash));
}
}

export function vaaHashToArray(vaaHash: VaaHash): Array<number> {
if (Array.isArray(vaaHash)) {
return vaaHash;
} else {
return Array.from(vaaHashToUint8Array(vaaHash));
}
}
export const vaaHash = (vaa: VAA<any>): VaaHash => keccak256(vaa.hash);

export async function reclaimCctpMessageIx(
messageTransmitter: MessageTransmitterProgram,
Expand All @@ -76,10 +44,7 @@ export async function reclaimCctpMessageIx(
const { payer, cctpMessage: messageSentEventData } = accounts;

return messageTransmitter.reclaimEventAccountIx(
{
payee: payer,
messageSentEventData,
},
{ payee: payer, messageSentEventData },
cctpAttestation,
);
}
Loading
Loading