[BUG] snarkyjs 0.2.0 allows multiple snapps to be deployed to the same address with shared state
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This demonstrates how to reproduce the bug reported in snarkyjs