Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/fuzzy-zones-bind.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Bound encrypted Zone deposits to the parent-chain portal caller.
8 changes: 5 additions & 3 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,9 @@ jobs:
tempo-tag: sha256:23150c7bae007afd2eeaa596c4b9f3d6950ecd0dec3e05ad44ebdbb8a6add863
zone-tag: sha-aae82c4
- hardfork: T10
# Tempo main (bb07377a) and Zones 60880b66.
tempo-tag: sha256:480dcf46f8805a13f9ec03371f8f1aa378942be77ec07e3e2f15a01a85b3dca4
zone-tag: sha256:a9e0ca9e9eaa0e52ee7c473237235054b0be9b07d5836d5393b9fceaf3fabd61
# Tempo de760926 and sender-bound Zones 196f47b7.
tempo-tag: sha256:af7a8955370adc4868a3237352ceded3bd917702a14758796eab86b338b75e49
zone-tag: sha256:45b4e66b0a7d5f8d9486eeb05e0842c7a223b53e9ef910ef73cd9558f4a79118

steps:
- name: Clone repository
Expand Down Expand Up @@ -365,6 +365,8 @@ jobs:
wagmi:
name: Wagmi
continue-on-error: true
# TODO: Re-enable Wagmi verification.
if: ${{ false }}
permissions:
contents: read
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ auditConfig:
# image-size has no patched release.
- GHSA-5p2g-fcmc-qvqq
- GHSA-w3rx-r6r6-pgpr
# extract-zip has no patched release.
- GHSA-jmr9-qjv8-65gv

overrides:
typescript: ^5.9.3
Expand Down
5 changes: 3 additions & 2 deletions site/pages/tempo/actions/zone.encryptedDeposit.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const receipt = await client.waitForTransactionReceipt({ hash })
Use `zone.encryptedDeposit.prepare` when one party should choose the private zone recipient, but another party will submit the parent-chain deposit transaction.

:::warning
Prepared encrypted deposits contain public transaction intent. If you receive a prepared payload from another party, validate `chainId`, `zoneId`, `portalAddress`, `token`, and `amount` before passing it to `zone.encryptedDeposit`.
Prepared encrypted deposits contain public transaction intent. If you receive a prepared payload from another party, validate `chainId`, `zoneId`, `portalAddress`, `sender`, `token`, and `amount` before passing it to `zone.encryptedDeposit`. The sender must match the account that calls the Zone portal.
:::

:::code-group
Expand All @@ -65,6 +65,7 @@ import { broadcaster, client } from './viem.config'
const prepared = await client.zone.encryptedDeposit.prepare({
amount: parseUnits('100', 6),
recipient: '0x0000000000000000000000000000000000000001',
sender: broadcaster.account.address,
token: '0x20c0000000000000000000000000000000000001',
zoneId: 7,
})
Expand All @@ -82,7 +83,7 @@ export const broadcaster = client

### Prepare Only the Encrypted Recipient

Use `zone.encryptedDeposit.prepareRecipient` when another contract or service controls token movement and only needs the ZonePortal encryption fields.
Use `zone.encryptedDeposit.prepareRecipient` when another contract or service controls token movement and only needs the ZonePortal encryption fields. The sender defaults to `client.account.address`; set it explicitly when another account will call the Zone portal.

```ts twoslash
import { client } from './viem.config'
Expand Down
1 change: 1 addition & 0 deletions site/pages/tempo/guides/earn/zones.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ const encrypted = await parentClient.zone.encryptedDeposit.prepare({
amount: earnShareAmount,
bouncebackRecipient: parentClient.account.address,
recipient: zoneClient.account.address,
sender: parentClient.account.address,
token: vault.shareToken,
zoneId,
})
Expand Down
2 changes: 2 additions & 0 deletions src/tempo/actions/earn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,7 @@ export namespace privateDeposit {
memo: returnMemo,
portalAddress: config.zonePortal,
recipient,
sender: gateway,
zoneId: config.zoneId,
})
const shareAmountMin = resolveMinimumShareAmount(parameters)
Expand Down Expand Up @@ -2429,6 +2430,7 @@ export namespace privateRedeem {
memo: returnMemo,
portalAddress: config.zonePortal,
recipient,
sender: gateway,
zoneId: config.zoneId,
}),
(async () => {
Expand Down
18 changes: 18 additions & 0 deletions src/tempo/actions/zone.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const client = createClient({
chain: tempoModerato,
transport,
})
const publicClient = createClient({
chain: tempoModerato,
transport,
})
const zoneClient = createClient({
account: '0x0000000000000000000000000000000000000001',
chain: zoneModerato(7),
Expand Down Expand Up @@ -60,6 +64,20 @@ test('encryptedDeposit.prepareRecipient returns reusable encrypted recipient dat
).toEqualTypeOf<zoneActions.PreparedEncryptedDepositRecipient>()
})

test('encryptedDeposit preparation requires a sender without a client account', async () => {
// @ts-expect-error sender is required when the client has no account
await zoneActions.encryptedDeposit.prepareRecipient(publicClient, {
recipient: '0x0000000000000000000000000000000000000001',
zoneId: 7,
})

await zoneActions.encryptedDeposit.prepareRecipient(publicClient, {
recipient: '0x0000000000000000000000000000000000000001',
sender: '0x0000000000000000000000000000000000000002',
zoneId: 7,
})
})

test('requestWithdrawal.prepare returns a request, maximum fee, and details', async () => {
const prepared = await zoneActions.requestWithdrawal.prepare(zoneClient, {
token: '0x20c0000000000000000000000000000000000000',
Expand Down
87 changes: 52 additions & 35 deletions src/tempo/actions/zone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,15 @@ const preparedEncryptedDeposit = {
},
keyIndex: 0n,
portalAddress,
sender: account.address,
token: '0x20c0000000000000000000000000000000000000',
zoneId,
} satisfies zoneActions.PreparedEncryptedDeposit
const prepareEncryptedDepositParameters = {
amount: parseUnits('1', 6),
bouncebackRecipient: account.address,
recipient: account.address,
sender: account.address,
token: parentToken,
zoneId: 7,
} as const
Expand All @@ -107,12 +109,15 @@ async function ensureZoneBalance(zoneToken: Address, minimumBalance: bigint) {
})
if (balance.amount >= minimumBalance) return

await zoneActions.depositSync(mainnetClient, {
const parameters = {
amount: parseUnits('1', 6),
portalAddress,
token: parentToken,
zoneId,
})
} as const
if (legacyZoneCallback)
await zoneActions.depositSync(mainnetClient, parameters)
else await zoneActions.encryptedDepositSync(mainnetClient, parameters)

for (let attempt = 0; attempt < 150; attempt++) {
const nextBalance = await tokenActions.getBalance(zoneClient, {
Expand Down Expand Up @@ -566,12 +571,14 @@ describe('encryptedDeposit', () => {
})

test('behavior: sends a prepared encrypted deposit', async () => {
const { sender: _, ...parameters } = prepareEncryptedDepositParameters
const prepared = await zoneActions.encryptedDeposit.prepare(mainnetClient, {
...prepareEncryptedDepositParameters,
...parameters,
portalAddress,
zoneId,
})

expect(prepared.sender).toBe(mainnetClient.account.address)
const hash = await zoneActions.encryptedDeposit(mainnetClient, prepared)
const receipt = await waitForTransactionReceipt(mainnetClient, { hash })

Expand All @@ -590,6 +597,7 @@ describe('encryptedDeposit', () => {

expect(prepared.chainId).toBe(chain.id)
expect(prepared.portalAddress).toBe(portalAddress)
expect(prepared.sender).toBe(mainnetClient.account.address)
expect(prepared.zoneId).toBe(zoneId)
expect(prepared.keyIndex).toBeGreaterThanOrEqual(0n)
expect(prepared.encrypted.ciphertext).toBeDefined()
Expand Down Expand Up @@ -702,36 +710,42 @@ describe('deposit', () => {
expect(registryCalls[1].address).toBe(getPortalAddress(tempoModerato.id, 7))
})

test('behavior: defaults bounceback recipient to account', async () => {
const client = createClient({
chain,
pollingInterval: 100,
transport: http(),
})
test.runIf(legacyZoneCallback)(
'behavior: defaults bounceback recipient to account',
async () => {
const client = createClient({
chain,
pollingInterval: 100,
transport: http(),
})

const hash = await zoneActions.deposit(client, {
...depositParameters,
account,
})
const receipt = await waitForTransactionReceipt(client, { hash })
const call = await getPortalCall(hash)
const hash = await zoneActions.deposit(client, {
...depositParameters,
account,
})
const receipt = await waitForTransactionReceipt(client, { hash })
const call = await getPortalCall(hash)

expect(receipt.status).toBe('success')
expect(call.functionName).toBe('deposit')
expect(call.args[4]).toBe(account.address)
})
expect(receipt.status).toBe('success')
expect(call.functionName).toBe('deposit')
expect(call.args[4]).toBe(account.address)
},
)

test('behavior: deposits tokens into zone via parent chain', async () => {
const result = await zoneActions.depositSync(mainnetClient, {
token: parentToken,
amount: parseUnits('1', 6),
portalAddress,
zoneId,
})
test.runIf(legacyZoneCallback)(
'behavior: deposits tokens into zone via parent chain',
async () => {
const result = await zoneActions.depositSync(mainnetClient, {
token: parentToken,
amount: parseUnits('1', 6),
portalAddress,
zoneId,
})

expect(result.receipt).toBeDefined()
expect(result.receipt.status).toBe('success')
})
expect(result.receipt).toBeDefined()
expect(result.receipt.status).toBe('success')
},
)

test('error: no account', async () => {
const noAccountClient = createClient({
Expand Down Expand Up @@ -1168,12 +1182,15 @@ describe('earn', () => {
const assetAmount = parseUnits('10', 6)
const assetDepositAmount =
assetAmount + withdrawalFee * 2n + parseUnits('10', 6)
const assetDeposit = await Actions.zone.depositSync(mainnetClient, {
amount: assetDepositAmount,
portalAddress,
token: addresses.alphaUsd,
zoneId,
})
const assetDeposit = await Actions.zone.encryptedDepositSync(
mainnetClient,
{
amount: assetDepositAmount,
portalAddress,
token: addresses.alphaUsd,
zoneId,
},
)
await Actions.zone.waitForTempoBlock(zoneClient, {
pollingInterval: 100,
tempoBlockNumber: assetDeposit.receipt.blockNumber,
Expand Down
Loading
Loading