Skip to content
This repository has been archived by the owner on Jun 29, 2023. It is now read-only.

Allow disputing Create2Transfer with nonexistent receiver #670

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions contracts/libs/Authenticity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ library Authenticity {
using Tx for bytes;
using Types for Types.UserState;

bytes32 public constant ZERO_BYTES32 =
0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563;

function verifyTransfer(
Types.AuthCommon memory common,
Types.SignatureProof memory proof
Expand Down Expand Up @@ -184,6 +187,10 @@ library Authenticity {
"Authenticity: to account does not exists"
);

if (proof.pubkeyHashesReceiver[i] == ZERO_BYTES32) {
return Types.Result.NonexistentReceiver;
}

// construct the message

uint256 nonce = proof.states[i].nonce - 1;
Expand Down
3 changes: 2 additions & 1 deletion contracts/libs/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ library Types {
BadWithdrawRoot,
BadCompression,
TooManyTx,
BadPrecompileCall
BadPrecompileCall,
NonexistentReceiver
}
}
85 changes: 83 additions & 2 deletions test/slow/rollup.create2Transfer.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { deployAll } from "../../ts/deploy";
import { TESTING_PARAMS } from "../../ts/constants";
import { TESTING_PARAMS, ZERO_BYTES32 } from "../../ts/constants";
import { ethers } from "hardhat";
import { StateTree } from "../../ts/stateTree";
import { AccountRegistry } from "../../ts/accountTree";
Expand All @@ -14,9 +14,14 @@ import {
} from "../../ts/commitments";
import { USDT } from "../../ts/decimal";
import { hexToUint8Array } from "../../ts/utils";
import { Group, txCreate2TransferFactory } from "../../ts/factory";
import {
Group,
txCreate2TransferFactory,
txCreate2TransferToNonexistentReceiver
} from "../../ts/factory";
import { deployKeyless } from "../../ts/deployment/deploy";
import { handleNewBatch } from "../../ts/client/batchHandler";
import { Result } from "../../ts/interfaces";

chai.use(chaiAsPromised);

Expand Down Expand Up @@ -177,4 +182,80 @@ describe("Rollup Create2Transfer", async function() {
"Good state transition should not rollback"
);
}).timeout(120000);

it("submit create2Transfer batch to nonexistent receiver and dispute", async function() {
const feeReceiver = usersWithStates.getUser(0).stateID;
const { rollup } = contracts;

const {
txs,
signature,
sender
} = txCreate2TransferToNonexistentReceiver(
usersWithStates,
usersWithoutState
);

stateTree.processCreate2TransferCommit(txs, feeReceiver);
const postStateRoot = stateTree.root;
const serialized = serialize(txs);

const root = registry.root();
const rootOnchain = await registry.registry.root();
assert.equal(root, rootOnchain, "mismatch pubkey tree root");

const commitment = Create2TransferCommitment.new(
postStateRoot,
root,
signature,
feeReceiver,
serialized
);

const targetBatch = commitment.toBatch();
const c2TBatchID = 1;
const _txSubmit = await targetBatch.submit(
rollup,
c2TBatchID,
TESTING_PARAMS.STAKE_AMOUNT
);
console.log(
"submitBatch execution cost",
(await _txSubmit.wait()).gasUsed.toNumber()
);

const commitmentMP = targetBatch.proof(0);
const postProofs = txs.map(tx => stateTree.getState(tx.fromIndex));
const proof = {
states: postProofs.map(proof => proof.state),
stateWitnesses: postProofs.map(proof => proof.witness),
pubkeysSender: [sender.pubkey],
pubkeyWitnessesSender: [registry.witness(sender.pubkeyID)],
pubkeyHashesReceiver: [ZERO_BYTES32],
pubkeyWitnessesReceiver: txs.map(tx =>
registry.witness(tx.toPubkeyID)
)
};
const _tx = await rollup.disputeSignatureCreate2Transfer(
c2TBatchID,
commitmentMP,
proof,
{ gasLimit: 1000000 }
);

const receipt = await _tx.wait();
console.log("disputeBatch execution cost", receipt.gasUsed.toNumber());
const [[status], [event]] = await Promise.all([
rollup.queryFilter(rollup.filters.RollbackStatus(), _tx.blockHash),
rollup.queryFilter(
rollup.filters.RollbackTriggered(),
_tx.blockHash
)
]);
assert.equal(Number(event.args.batchID), c2TBatchID);
assert.equal(Number(await rollup.invalidBatchMarker()), 0);
assert.isTrue(status.args?.completed);
assert.equal(Number(status.args?.nDeleted), 1);
assert.equal(event.args.result, Result.NonexistentReceiver);
}).timeout(120000);
});
43 changes: 42 additions & 1 deletion ts/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PubkeyDatabaseEngine, StateDatabaseEngine } from "./client/database";
import { StorageManager } from "./client/storageEngine";
import { BatchMemoryStorage } from "./client/storageEngine/batches/memory";
import { TransactionMemoryStorage } from "./client/storageEngine/transactions/memory";
import { DEFAULT_MNEMONIC } from "./constants";
import { DEFAULT_MNEMONIC, ZERO_BYTES32 } from "./constants";
import { float16, USDT } from "./decimal";
import { UserNotExist } from "./exceptions";
import { Domain, solG1 } from "./mcl";
Expand All @@ -17,6 +17,7 @@ import {
SignableTx,
getAggregateSig
} from "./tx";
import { solidityPack } from "ethers/lib/utils";

export class User {
private tokenIDtoStateID: Record<number, number>;
Expand Down Expand Up @@ -303,6 +304,46 @@ export function txMassMigrationFactory(
return { txs, signature, senders };
}

export function txCreate2TransferToNonexistentReceiver(
registered: Group,
unregistered: Group
): {
txs: TxCreate2Transfer[];
signature: solG1;
sender: User;
} {
const sender = registered.getUser(0);
const receiver = unregistered.getUser(0);
const senderState = registered.getState(sender);
const amount = float16.round(senderState.balance.div(10));
const fee = float16.round(amount.div(10));

const tx = new TxCreate2Transfer(
sender.stateID,
receiver.stateID,
receiver.pubkey,
1000,
amount,
fee,
senderState.nonce.toNumber()
);
const txMessage = create2TransferMessage(tx, ZERO_BYTES32);
tx.signature = sender.signRaw(txMessage);
const txs = [tx];

return { txs: txs, signature: getAggregateSig(txs), sender };
}

function create2TransferMessage(
tx: TxCreate2Transfer,
pubkeyHash: string
): string {
return solidityPack(
["uint256", "uint256", "bytes32", "uint256", "uint256", "uint256"],
["0x03", tx.fromIndex, pubkeyHash, tx.nonce, tx.amount, tx.fee]
);
}

interface StorageManagerFactoryOptions {
stateTreeDepth?: number;
pubkeyTreeDepth?: number;
Expand Down
4 changes: 3 additions & 1 deletion ts/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export enum Result {
MismatchedAmount,
BadWithdrawRoot,
BadCompression,
TooManyTx
TooManyTx,
BadPrecompileCall,
NonexistentReceiver
}

export type Wei = string;
Expand Down