Skip to content

Instantly share code, notes, and snippets.

@mirceanis
Last active January 21, 2022 12:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mirceanis/b53b6acd0dbe6f1658c78706800736d5 to your computer and use it in GitHub Desktop.
Save mirceanis/b53b6acd0dbe6f1658c78706800736d5 to your computer and use it in GitHub Desktop.
[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

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