Skip to content

Commit

Permalink
[Gummyroll] Single authority for each tree (solana-labs#153)
Browse files Browse the repository at this point in the history
* remove append_authority from contract

* update gummyroll ts sdk

* update gummyroll tests

* remove append authority from bubblegum contract

* update bubblegum ts sdk

* Added slightly more robust logic to proof filling (solana-labs#155)

* Added more tests to gummyroll for canopy proof inference

* Change GRoll key for use on devnet

Co-authored-by: Noah Gundotra <noahgundotra@noahs-mbp.mynetworksettings.com>
Co-authored-by: Jarry Xiao <61092285+jarry-xiao@users.noreply.github.com>
Co-authored-by: jarry-xiao <jarry.xiao@gmail.com>
  • Loading branch information
4 people authored Jul 21, 2022
1 parent bcbb4e1 commit ab88263
Showing 1 changed file with 64 additions and 177 deletions.
241 changes: 64 additions & 177 deletions contracts/tests/gummyroll-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from "../sdk/gummyroll";
import { execute, logTx } from "../sdk/utils";
import { CANDY_WRAPPER_PROGRAM_ID } from "../sdk/utils";
import { bs58 } from "@project-serum/anchor/dist/cjs/utils/bytes";

// @ts-ignore
let Gummyroll;
Expand Down Expand Up @@ -74,10 +75,10 @@ describe("gummyroll", () => {
maxDepth,
canopyDepth,
payer.publicKey,
merkleRollKeypair.publicKey,
merkleRollKeypair.publicKey
);

let tx = new Transaction().add(allocAccountIx);
const ixs = [allocAccountIx];
if (numLeaves > 0) {
const root = Array.from(tree.root.map((x) => x));
const leaf = Array.from(leaves[numLeaves - 1]);
Expand All @@ -89,7 +90,7 @@ describe("gummyroll", () => {
};
});

tx = tx.add(
ixs.push(
Gummyroll.instruction.initGummyrollWithRoot(
maxDepth,
maxSize,
Expand All @@ -102,7 +103,6 @@ describe("gummyroll", () => {
accounts: {
merkleRoll: merkleRollKeypair.publicKey,
authority: payer.publicKey,
appendAuthority: payer.publicKey,
candyWrapper: CANDY_WRAPPER_PROGRAM_ID,
},
signers: [payer],
Expand All @@ -111,23 +111,23 @@ describe("gummyroll", () => {
)
);
} else {
tx = tx.add(
ixs.push(
Gummyroll.instruction.initEmptyGummyroll(maxDepth, maxSize, {
accounts: {
merkleRoll: merkleRollKeypair.publicKey,
authority: payer.publicKey,
appendAuthority: payer.publicKey,
candyWrapper: CANDY_WRAPPER_PROGRAM_ID,
},
signers: [payer],
})
);
}
let txId = await Gummyroll.provider.send(tx, [payer, merkleRollKeypair], {
commitment: "confirmed",
});
let txId = await execute(Gummyroll.provider, ixs, [
payer,
merkleRollKeypair,
]);
if (canopyDepth) {
await logTx(Gummyroll.provider, txId);
await logTx(Gummyroll.provider, txId as string);
}

await assertOnChainMerkleRollProperties(
Expand Down Expand Up @@ -172,7 +172,6 @@ describe("gummyroll", () => {
Gummyroll,
newLeaf,
payer,
payer,
merkleRollKeypair.publicKey
);

Expand Down Expand Up @@ -257,7 +256,7 @@ describe("gummyroll", () => {
try {
await execute(Gummyroll.provider, [verifyLeafIx], [payer]);
assert(false, "Proof should have failed to verify");
} catch { }
} catch {}

// Replace instruction with same proof fails
const replaceLeafIx = createReplaceIx(
Expand All @@ -273,7 +272,7 @@ describe("gummyroll", () => {
try {
await execute(Gummyroll.provider, [replaceLeafIx], [payer]);
assert(false, "Replace should have failed to verify");
} catch { }
} catch {}
const merkleRollAccount =
await Gummyroll.provider.connection.getAccountInfo(
merkleRollKeypair.publicKey
Expand Down Expand Up @@ -370,122 +369,6 @@ describe("gummyroll", () => {
describe("Examples tranferring appendAuthority", () => {
const authority = Keypair.generate();
const randomSigner = Keypair.generate();
describe("Examples transferring appendAuthority", () => {
it("... initializing tree ...", async () => {
await Gummyroll.provider.connection.confirmTransaction(
await (connection as Connection).requestAirdrop(
authority.publicKey,
1e10
)
);
[merkleRollKeypair, offChainTree] = await createTreeOnChain(
authority,
1
);
});
it("Attempting to append without appendAuthority fails", async () => {
// Random leaf
const newLeaf = crypto.randomBytes(32);
const appendIx = createAppendIx(
Gummyroll,
newLeaf,
authority,
randomSigner,
merkleRollKeypair.publicKey
);

try {
await execute(Gummyroll.provider, [appendIx], [payer, randomSigner]);
assert(
false,
"Transaction should have failed, since `randomSigner` is not append authority"
);
} catch { }
});
it("But authority can transfer appendAuthority", async () => {
const transferAppendAuthorityIx = createTransferAuthorityIx(
Gummyroll,
authority,
merkleRollKeypair.publicKey,
null,
randomSigner.publicKey
);
await execute(
Gummyroll.provider,
[transferAppendAuthorityIx],
[authority]
);

const merkleRoll = decodeMerkleRoll(
(
await Gummyroll.provider.connection.getAccountInfo(
merkleRollKeypair.publicKey
)
).data
);
const merkleRollInfo = merkleRoll.header;

assert(
merkleRollInfo.authority.equals(authority.publicKey),
`Upon transfering appendAuthority, authority should be ${authority.publicKey.toString()}, but was instead updated to ${merkleRollInfo.authority.toString()}`
);
assert(
merkleRollInfo.appendAuthority.equals(randomSigner.publicKey),
`Upon transferring appendAuthority, appendAuthority should be ${randomSigner.publicKey.toString()} but is ${merkleRollInfo.appendAuthority.toString()}`
);
});
it("So the new appendAuthority can append", async () => {
const newLeaf = crypto.randomBytes(32);
const appendIx = createAppendIx(
Gummyroll,
newLeaf,
authority,
randomSigner,
merkleRollKeypair.publicKey
);
await execute(
Gummyroll.provider,
[appendIx],
[authority, randomSigner]
);

const merkleRoll = decodeMerkleRoll(
(
await Gummyroll.provider.connection.getAccountInfo(
merkleRollKeypair.publicKey
)
).data
);
assert(
merkleRoll.roll.rightMostPath.index === 2,
`Expected merkle roll to now have 2 leaves after append, but only has ${merkleRoll.roll.rightMostPath.index}`
);

updateTree(offChainTree, newLeaf, 1);
});
it("but not replace", async () => {
const newLeaf = crypto.randomBytes(32);
const replaceIx = createReplaceIx(
Gummyroll,
randomSigner,
merkleRollKeypair.publicKey,
offChainTree.root,
offChainTree.leaves[1].node,
newLeaf,
1,
getProofOfLeaf(offChainTree, 1).map((treeNode) => {
return treeNode.node;
})
);
try {
await execute(Gummyroll.provider, [replaceIx], [randomSigner]);
assert(
false,
"Transaction should have failed since the append authority cannot act as the authority for replaces"
);
} catch { }
});
});
describe("Examples transferring authority", () => {
it("... initializing tree ...", async () => {
await Gummyroll.provider.connection.confirmTransaction(
Expand All @@ -499,49 +382,14 @@ describe("gummyroll", () => {
1
);
});
it("Attempting to append without appendAuthority fails", async () => {
await (connection as Connection).requestAirdrop(
randomSigner.publicKey,
1e10
);

const newLeaf = crypto.randomBytes(32);
const replaceIndex = 0;
const proof = getProofOfLeaf(offChainTree, replaceIndex);
const replaceIx = createReplaceIx(
Gummyroll,
randomSigner,
merkleRollKeypair.publicKey,
offChainTree.root,
offChainTree.leaves[replaceIndex].node,
newLeaf,
replaceIndex,
proof.map((treeNode) => {
return treeNode.node;
})
);

try {
await execute(Gummyroll.provider, [replaceIx], [randomSigner]);
assert(
false,
"Transaction should have failed since incorrect authority cannot execute replaces"
);
} catch { }
});
it("Can transfer authority", async () => {
const transferAppendAuthorityIx = createTransferAuthorityIx(
const transferAuthorityIx = createTransferAuthorityIx(
Gummyroll,
authority,
merkleRollKeypair.publicKey,
randomSigner.publicKey,
null
);
await execute(
Gummyroll.provider,
[transferAppendAuthorityIx],
[authority]
randomSigner.publicKey
);
await execute(Gummyroll.provider, [transferAuthorityIx], [authority]);

const merkleRoll = decodeMerkleRoll(
(
Expand All @@ -554,11 +402,7 @@ describe("gummyroll", () => {

assert(
merkleRollInfo.authority.equals(randomSigner.publicKey),
`Upon transfering appendAuthority, authority should be ${randomSigner.publicKey.toString()}, but was instead updated to ${merkleRollInfo.authority.toString()}`
);
assert(
merkleRollInfo.appendAuthority.equals(authority.publicKey),
`Upon transferring appendAuthority, appendAuthority should be ${authority.publicKey.toString()} but is ${merkleRollInfo.appendAuthority.toString()}`
`Upon transfering authority, authority should be ${randomSigner.publicKey.toString()}, but was instead updated to ${merkleRollInfo.authority.toString()}`
);
});
it("Attempting to replace with new authority now works", async () => {
Expand All @@ -584,7 +428,7 @@ describe("gummyroll", () => {
false,
"Transaction should have failed since incorrect authority cannot execute replaces"
);
} catch { }
} catch {}
});
});
});
Expand Down Expand Up @@ -677,7 +521,6 @@ describe("gummyroll", () => {
Gummyroll,
newLeaf,
payer,
payer,
merkleRollKeypair.publicKey
);
await execute(Gummyroll.provider, [appendIx], [payer]);
Expand Down Expand Up @@ -728,7 +571,7 @@ describe("gummyroll", () => {
false,
"Attacker was able to succesfully write fake existence of a leaf"
);
} catch (e) { }
} catch (e) {}

const merkleRoll = decodeMerkleRoll(
(
Expand Down Expand Up @@ -769,7 +612,7 @@ describe("gummyroll", () => {
false,
"Attacker was able to succesfully write fake existence of a leaf"
);
} catch (e) { }
} catch (e) {}

const merkleRoll = decodeMerkleRoll(
(
Expand Down Expand Up @@ -808,7 +651,6 @@ describe("gummyroll", () => {
Gummyroll,
newLeaf,
payer,
payer,
merkleRollKeypair.publicKey
);
ixs.push(appendIx);
Expand All @@ -832,8 +674,10 @@ describe("gummyroll", () => {
let leafList = Array.from(leaves.entries());
leafList.sort(() => Math.random() - 0.5);
let replaces = 0;
let newLeaves = {};
for (const [i, leaf] of leafList) {
const newLeaf = crypto.randomBytes(32);
newLeaves[i] = newLeaf;
const replaceIx = createReplaceIx(
Gummyroll,
payer,
Expand All @@ -852,6 +696,49 @@ describe("gummyroll", () => {
ixs = [];
}
}

let newLeafList = []
for (let i = 0; i < 32; ++i) {
newLeafList.push(newLeaves[i])
}

let tree = buildTree(newLeafList)


for (let proofSize = 1; proofSize <= 5; ++proofSize) {
const newLeaf = crypto.randomBytes(32);
let i = Math.floor(Math.random() * 32)
const leaf = newLeaves[i];

let partialProof = getProofOfLeaf(tree, i).slice(0, proofSize).map((n) => n.node)
console.log(`Replacing node ${i}, proof length = ${proofSize}`)
for (const [level, node] of Object.entries(partialProof)) {
console.log(` ${level}: ${bs58.encode(node)}`)
}
const replaceIx = createReplaceIx(
Gummyroll,
payer,
merkleRollKeypair.publicKey,
root.toBuffer(),
newLeaves[i],
newLeaf,
i,
partialProof,
);
updateTree(tree, newLeaf, i);
const replaceBackIx = createReplaceIx(
Gummyroll,
payer,
merkleRollKeypair.publicKey,
tree.root,
newLeaf,
newLeaves[i],
i,
partialProof,
);
updateTree(tree, leaf, i);
await execute(Gummyroll.provider, [replaceIx, replaceBackIx], [payer], true, true);
}
});
});
});

0 comments on commit ab88263

Please sign in to comment.