-
Notifications
You must be signed in to change notification settings - Fork 265
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
test: persistence uses TokenContract #3930
Changes from 4 commits
7fd5762
a33238c
75e451b
0f24dc6
2e3b491
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,15 @@ | ||
import { getUnsafeSchnorrAccount, getUnsafeSchnorrWallet } from '@aztec/accounts/single_key'; | ||
import { AccountWallet, waitForAccountSynch } from '@aztec/aztec.js'; | ||
import { | ||
AccountWallet, | ||
ExtendedNote, | ||
Note, | ||
TxHash, | ||
computeMessageSecretHash, | ||
waitForAccountSynch, | ||
} from '@aztec/aztec.js'; | ||
import { CompleteAddress, EthAddress, Fq, Fr } from '@aztec/circuits.js'; | ||
import { DeployL1Contracts } from '@aztec/ethereum'; | ||
import { EasyPrivateTokenContract } from '@aztec/noir-contracts/EasyPrivateToken'; | ||
import { TokenContract } from '@aztec/noir-contracts/Token'; | ||
|
||
import { mkdtemp } from 'fs/promises'; | ||
import { tmpdir } from 'os'; | ||
|
@@ -14,13 +21,14 @@ describe('Aztec persistence', () => { | |
/** | ||
* These tests check that the Aztec Node and PXE can be shutdown and restarted without losing data. | ||
* | ||
* There are four scenarios to check: | ||
* There are five scenarios to check: | ||
* 1. Node and PXE are started with an existing databases | ||
* 2. PXE is started with an existing database and connects to a Node with an empty database | ||
* 3. PXE is started with an empty database and connects to a Node with an existing database | ||
* 4. PXE is started with an empty database and connects to a Node with an empty database | ||
* 5. Node and PXE are started with existing databases, but the chain has advanced since they were shutdown | ||
* | ||
* All four scenarios use the same L1 state, which is deployed in the `beforeAll` hook. | ||
* All five scenarios use the same L1 state, which is deployed in the `beforeAll` hook. | ||
*/ | ||
|
||
// the test contract and account deploying it | ||
|
@@ -48,12 +56,30 @@ describe('Aztec persistence', () => { | |
const ownerWallet = await getUnsafeSchnorrAccount(initialContext.pxe, ownerPrivateKey, Fr.ZERO).waitDeploy(); | ||
ownerAddress = ownerWallet.getCompleteAddress(); | ||
|
||
const deployer = EasyPrivateTokenContract.deploy(ownerWallet, 1000n, ownerWallet.getAddress()); | ||
const deployer = TokenContract.deploy(ownerWallet, ownerWallet.getAddress(), 'Test token', 'TEST', 2); | ||
await deployer.simulate({}); | ||
|
||
const contract = await deployer.send().deployed(); | ||
contractAddress = contract.completeAddress; | ||
|
||
const secret = Fr.random(); | ||
|
||
const mintTx = contract.methods.mint_private(1000n, computeMessageSecretHash(secret)); | ||
await mintTx.simulate(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that you don't need to explicitly call |
||
const mintTxReceipt = await mintTx.send().wait(); | ||
|
||
await addPendingShieldNoteToPXE( | ||
ownerWallet, | ||
contractAddress, | ||
1000n, | ||
computeMessageSecretHash(secret), | ||
mintTxReceipt.txHash, | ||
); | ||
|
||
const redeemTx = contract.methods.redeem_shield(ownerAddress.address, 1000n, secret); | ||
await redeemTx.simulate(); | ||
await redeemTx.send().wait(); | ||
|
||
await initialContext.teardown(); | ||
}, 100_000); | ||
|
||
|
@@ -72,37 +98,57 @@ describe('Aztec persistence', () => { | |
], | ||
])('%s', (_, contextSetup, timeout) => { | ||
let ownerWallet: AccountWallet; | ||
let contract: EasyPrivateTokenContract; | ||
let contract: TokenContract; | ||
|
||
beforeEach(async () => { | ||
context = await contextSetup(); | ||
ownerWallet = await getUnsafeSchnorrWallet(context.pxe, ownerAddress.address, ownerPrivateKey); | ||
contract = await EasyPrivateTokenContract.at(contractAddress.address, ownerWallet); | ||
contract = await TokenContract.at(contractAddress.address, ownerWallet); | ||
}, timeout); | ||
|
||
afterEach(async () => { | ||
await context.teardown(); | ||
}); | ||
|
||
it('correctly restores balances', async () => { | ||
it('correctly restores private notes', async () => { | ||
// test for >0 instead of exact value so test isn't dependent on run order | ||
await expect(contract.methods.getBalance(ownerWallet.getAddress()).view()).resolves.toBeGreaterThan(0n); | ||
await expect(contract.methods.balance_of_private(ownerWallet.getAddress()).view()).resolves.toBeGreaterThan(0n); | ||
}); | ||
|
||
it('correctly restores public storage', async () => { | ||
await expect(contract.methods.total_supply().view()).resolves.toBeGreaterThan(0n); | ||
}); | ||
|
||
it('tracks new notes for the owner', async () => { | ||
const balance = await contract.methods.getBalance(ownerWallet.getAddress()).view(); | ||
await contract.methods.mint(1000n, ownerWallet.getAddress()).send().wait(); | ||
await expect(contract.methods.getBalance(ownerWallet.getAddress()).view()).resolves.toEqual(balance + 1000n); | ||
const balance = await contract.methods.balance_of_private(ownerWallet.getAddress()).view(); | ||
|
||
const secret = Fr.random(); | ||
const mintTxReceipt = await contract.methods.mint_private(1000n, computeMessageSecretHash(secret)).send().wait(); | ||
await addPendingShieldNoteToPXE( | ||
ownerWallet, | ||
contractAddress, | ||
1000n, | ||
computeMessageSecretHash(secret), | ||
mintTxReceipt.txHash, | ||
); | ||
|
||
await contract.methods.redeem_shield(ownerWallet.getAddress(), 1000n, secret).send().wait(); | ||
|
||
await expect(contract.methods.balance_of_private(ownerWallet.getAddress()).view()).resolves.toEqual( | ||
balance + 1000n, | ||
); | ||
}); | ||
|
||
it('allows transfers of tokens from owner', async () => { | ||
it('allows spending of private notes', async () => { | ||
const otherWallet = await getUnsafeSchnorrAccount(context.pxe, Fq.random(), Fr.ZERO).waitDeploy(); | ||
|
||
const initialOwnerBalance = await contract.methods.getBalance(ownerWallet.getAddress()).view(); | ||
await contract.methods.transfer(500n, ownerWallet.getAddress(), otherWallet.getAddress()).send().wait(); | ||
const initialOwnerBalance = await contract.methods.balance_of_private(ownerWallet.getAddress()).view(); | ||
|
||
await contract.methods.transfer(ownerWallet.getAddress(), otherWallet.getAddress(), 500n, Fr.ZERO).send().wait(); | ||
|
||
const [ownerBalance, targetBalance] = await Promise.all([ | ||
contract.methods.getBalance(ownerWallet.getAddress()).view(), | ||
contract.methods.getBalance(otherWallet.getAddress()).view(), | ||
contract.methods.balance_of_private(ownerWallet.getAddress()).view(), | ||
contract.methods.balance_of_private(otherWallet.getAddress()).view(), | ||
]); | ||
|
||
expect(ownerBalance).toEqual(initialOwnerBalance - 500n); | ||
|
@@ -143,42 +189,158 @@ describe('Aztec persistence', () => { | |
await context.pxe.registerRecipient(ownerAddress); | ||
|
||
const wallet = await getUnsafeSchnorrAccount(context.pxe, Fq.random(), Fr.ZERO).waitDeploy(); | ||
const contract = await EasyPrivateTokenContract.at(contractAddress.address, wallet); | ||
await expect(contract.methods.getBalance(ownerAddress.address).view()).rejects.toThrowError(/Unknown contract/); | ||
const contract = await TokenContract.at(contractAddress.address, wallet); | ||
await expect(contract.methods.balance_of_private(ownerAddress.address).view()).rejects.toThrowError( | ||
/Unknown contract/, | ||
); | ||
}); | ||
|
||
it("pxe does not have owner's notes", async () => { | ||
it("pxe does not have owner's private notes", async () => { | ||
await context.pxe.addContracts([ | ||
{ | ||
artifact: EasyPrivateTokenContract.artifact, | ||
artifact: TokenContract.artifact, | ||
completeAddress: contractAddress, | ||
portalContract: EthAddress.ZERO, | ||
}, | ||
]); | ||
await context.pxe.registerRecipient(ownerAddress); | ||
|
||
const wallet = await getUnsafeSchnorrAccount(context.pxe, Fq.random(), Fr.ZERO).waitDeploy(); | ||
const contract = await EasyPrivateTokenContract.at(contractAddress.address, wallet); | ||
await expect(contract.methods.getBalance(ownerAddress.address).view()).resolves.toEqual(0n); | ||
const contract = await TokenContract.at(contractAddress.address, wallet); | ||
await expect(contract.methods.balance_of_private(ownerAddress.address).view()).resolves.toEqual(0n); | ||
}); | ||
|
||
it('has access to public storage', async () => { | ||
await context.pxe.addContracts([ | ||
{ | ||
artifact: TokenContract.artifact, | ||
completeAddress: contractAddress, | ||
portalContract: EthAddress.ZERO, | ||
}, | ||
]); | ||
|
||
const wallet = await getUnsafeSchnorrAccount(context.pxe, Fq.random(), Fr.ZERO).waitDeploy(); | ||
const contract = await TokenContract.at(contractAddress.address, wallet); | ||
|
||
await expect(contract.methods.total_supply().view()).resolves.toBeGreaterThan(0n); | ||
}); | ||
|
||
it('pxe restores notes after registering the owner', async () => { | ||
await context.pxe.addContracts([ | ||
{ | ||
artifact: EasyPrivateTokenContract.artifact, | ||
artifact: TokenContract.artifact, | ||
completeAddress: contractAddress, | ||
portalContract: EthAddress.ZERO, | ||
}, | ||
]); | ||
|
||
await context.pxe.registerAccount(ownerPrivateKey, ownerAddress.partialAddress); | ||
const ownerWallet = await getUnsafeSchnorrAccount(context.pxe, ownerPrivateKey, ownerAddress).getWallet(); | ||
const contract = await EasyPrivateTokenContract.at(contractAddress.address, ownerWallet); | ||
const ownerAccount = getUnsafeSchnorrAccount(context.pxe, ownerPrivateKey, ownerAddress); | ||
await ownerAccount.register(); | ||
const ownerWallet = await ownerAccount.getWallet(); | ||
const contract = await TokenContract.at(contractAddress.address, ownerWallet); | ||
|
||
await waitForAccountSynch(context.pxe, ownerAddress, { interval: 1, timeout: 10 }); | ||
|
||
// check that notes total more than 0 so that this test isn't dependent on run order | ||
await expect(contract.methods.getBalance(ownerAddress.address).view()).resolves.toBeGreaterThan(0n); | ||
await expect(contract.methods.balance_of_private(ownerAddress.address).view()).resolves.toBeGreaterThan(0n); | ||
}); | ||
}); | ||
|
||
describe('when starting Node and PXE with existing databases, but chain has advanced since they were shutdown', () => { | ||
let secret: Fr; | ||
let mintTxHash: TxHash; | ||
let mintAmount: bigint; | ||
let revealedAmount: bigint; | ||
|
||
// The test system is shutdown. Its state is saved to disk | ||
// Start a temporary node and PXE, synch it and add the contract and account to it. | ||
// Perform some actions with these temporary components to advance the chain | ||
// Then shutdown the temporary components and restart the original components | ||
// They should sync up from where they left off and be able to see the actions performed by the temporary node & PXE. | ||
beforeAll(async () => { | ||
const temporaryContext = await setup(0, { deployL1ContractsValues }, {}); | ||
|
||
await temporaryContext.pxe.addContracts([ | ||
{ | ||
artifact: TokenContract.artifact, | ||
completeAddress: contractAddress, | ||
portalContract: EthAddress.ZERO, | ||
}, | ||
]); | ||
|
||
const ownerAccount = getUnsafeSchnorrAccount(temporaryContext.pxe, ownerPrivateKey, ownerAddress); | ||
await ownerAccount.register(); | ||
const ownerWallet = await ownerAccount.getWallet(); | ||
|
||
const contract = await TokenContract.at(contractAddress.address, ownerWallet); | ||
|
||
// mint some tokens with a secret we know and redeem later on a separate PXE | ||
secret = Fr.random(); | ||
mintAmount = 1000n; | ||
const mintTxReceipt = await contract.methods | ||
.mint_private(mintAmount, computeMessageSecretHash(secret)) | ||
.send() | ||
.wait(); | ||
mintTxHash = mintTxReceipt.txHash; | ||
|
||
// publicly reveal that I have 1000 tokens | ||
revealedAmount = 1000n; | ||
await contract.methods.unshield(ownerAddress, ownerAddress, revealedAmount, 0).send().wait(); | ||
|
||
// shut everything down | ||
await temporaryContext.teardown(); | ||
}, 100_000); | ||
|
||
let ownerWallet: AccountWallet; | ||
let contract: TokenContract; | ||
|
||
beforeEach(async () => { | ||
context = await setup(0, { dataDirectory, deployL1ContractsValues }, { dataDirectory }); | ||
ownerWallet = await getUnsafeSchnorrWallet(context.pxe, ownerAddress.address, ownerPrivateKey); | ||
contract = await TokenContract.at(contractAddress.address, ownerWallet); | ||
|
||
await waitForAccountSynch(context.pxe, ownerAddress, { interval: 0.1, timeout: 5 }); | ||
}, 5000); | ||
|
||
afterEach(async () => { | ||
await context.teardown(); | ||
}); | ||
|
||
it("restores owner's public balance", async () => { | ||
await expect(contract.methods.balance_of_public(ownerAddress.address).view()).resolves.toEqual(revealedAmount); | ||
}); | ||
|
||
it('allows consuming transparent note created on another PXE', async () => { | ||
// this was created in the temporary PXE in `beforeAll` | ||
await addPendingShieldNoteToPXE( | ||
ownerWallet, | ||
contractAddress, | ||
mintAmount, | ||
computeMessageSecretHash(secret), | ||
mintTxHash, | ||
); | ||
|
||
const balanceBeforeRedeem = await contract.methods.balance_of_private(ownerWallet.getAddress()).view(); | ||
|
||
await contract.methods.redeem_shield(ownerWallet.getAddress(), mintAmount, secret).send().wait(); | ||
const balanceAfterRedeem = await contract.methods.balance_of_private(ownerWallet.getAddress()).view(); | ||
|
||
expect(balanceAfterRedeem).toEqual(balanceBeforeRedeem + mintAmount); | ||
}); | ||
}); | ||
}); | ||
|
||
async function addPendingShieldNoteToPXE( | ||
wallet: AccountWallet, | ||
asset: CompleteAddress, | ||
amount: bigint, | ||
secretHash: Fr, | ||
txHash: TxHash, | ||
) { | ||
// The storage slot of `pending_shields` is 5. | ||
// TODO AlexG, this feels brittle | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It most definitely is, but we don't have a better way around it yet. Maybe we could extract it to a shared function, but that's about it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be cool if we extracted storage slots from the Noir code and made them available on the Contract interface. Something like this would be really nice to use async function addPendingShieldNoteToPXE(
wallet: AccountWallet,
asset: TokenContract,
amount: bigint,
secretHash: Fr,
txHash: TxHash,
) {
const note = new Note([new Fr(amount), secretHash]);
const extendedNote = new ExtendedNote(note, wallet.getAddress(), contract.address, contract.storageSlots.pendingShields, txHash);
await wallet.addNote(extendedNote);
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to #2806. Having a mapping of a contract storage available has popped up multiple times. |
||
const storageSlot = new Fr(5); | ||
const note = new Note([new Fr(amount), secretHash]); | ||
const extendedNote = new ExtendedNote(note, wallet.getAddress(), asset.address, storageSlot, txHash); | ||
await wallet.addNote(extendedNote); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is the best place to put it but what this is trying to solve is: adding an existing account, that's been deployed through PXE A, to a new PXE, B and calling contracts using this new PXE instance, B.
I'm not a fan of the random
portalContract: EthAddress.ZERO
in here 😟LMK if there's a better test utility to register an existing account with a new PXE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense! I doubt account contracts will have a corresponding portal, so I think this is ok.