Skip to content

Commit 0543b0c

Browse files
authored
[protocol 3.6] Allow initializing owner with account update txs (#1990)
1 parent 2e012bd commit 0543b0c

File tree

7 files changed

+91
-27
lines changed

7 files changed

+91
-27
lines changed

packages/loopring_v3.js/src/request_processors/account_update_processor.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ interface AccountUpdate {
2020
* Processes account update requests.
2121
*/
2222
export class AccountUpdateProcessor {
23-
public static process(state: ExchangeState, block: BlockContext, txData: Bitstream) {
23+
public static process(
24+
state: ExchangeState,
25+
block: BlockContext,
26+
txData: Bitstream
27+
) {
2428
const update = AccountUpdateProcessor.extractData(txData);
2529

2630
const account = state.getAccount(update.accountID);
31+
account.owner = update.owner;
2732
account.publicKeyX = update.publicKeyX;
2833
account.publicKeyY = update.publicKeyY;
2934
account.nonce++;
@@ -50,7 +55,10 @@ export class AccountUpdateProcessor {
5055
offset += 4;
5156
update.feeTokenID = data.extractUint16(offset);
5257
offset += 2;
53-
update.fee = fromFloat(data.extractUint16(offset), Constants.Float16Encoding);
58+
update.fee = fromFloat(
59+
data.extractUint16(offset),
60+
Constants.Float16Encoding
61+
);
5462
offset += 2;
5563
const publicKey = data.extractData(offset, 32);
5664
offset += 32;

packages/loopring_v3/circuit/Circuits/AccountUpdateCircuit.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
3636
Poseidon_gadget_T<9, 1, 6, 53, 8, 1> hash;
3737

3838
// Validate
39+
OwnerValidGadget ownerValid;
3940
RequireLtGadget requireValidUntil;
4041
RequireLeqGadget requireValidFee;
4142

@@ -67,7 +68,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
6768
: BaseTransactionCircuit(pb, state, prefix),
6869

6970
// Inputs
70-
owner(pb, state.accountA.account.owner, NUM_BITS_ADDRESS, FMT(prefix, ".owner")),
71+
owner(pb, NUM_BITS_ADDRESS, FMT(prefix, ".owner")),
7172
accountID(pb, NUM_BITS_ACCOUNT, FMT(prefix, ".accountID")),
7273
validUntil(pb, NUM_BITS_TIMESTAMP, FMT(prefix, ".validUntil")),
7374
nonce(pb, state.accountA.account.nonce, NUM_BITS_NONCE, FMT(prefix, ".nonce")),
@@ -93,6 +94,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
9394
FMT(this->annotation_prefix, ".hash")),
9495

9596
// Validate
97+
ownerValid(pb, state.constants, state.accountA.account.owner, owner.packed, FMT(prefix, ".ownerValid")),
9698
requireValidUntil(
9799
pb,
98100
state.timestamp,
@@ -145,6 +147,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
145147
{
146148
// Update the account data
147149
setArrayOutput(TXV_ACCOUNT_A_ADDRESS, accountID.bits);
150+
setOutput(TXV_ACCOUNT_A_OWNER, owner.packed);
148151
setOutput(TXV_ACCOUNT_A_PUBKEY_X, publicKeyX);
149152
setOutput(TXV_ACCOUNT_A_PUBKEY_Y, publicKeyY);
150153
setOutput(TXV_ACCOUNT_A_NONCE, nonce_after.result());
@@ -168,7 +171,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
168171
void generate_r1cs_witness(const AccountUpdateTx &update)
169172
{
170173
// Inputs
171-
owner.generate_r1cs_witness();
174+
owner.generate_r1cs_witness(pb, update.owner);
172175
accountID.generate_r1cs_witness(pb, update.accountID);
173176
validUntil.generate_r1cs_witness(pb, update.validUntil);
174177
nonce.generate_r1cs_witness();
@@ -183,6 +186,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
183186
hash.generate_r1cs_witness();
184187

185188
// Validate
189+
ownerValid.generate_r1cs_witness();
186190
requireValidUntil.generate_r1cs_witness();
187191
requireValidFee.generate_r1cs_witness();
188192

@@ -224,6 +228,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
224228
hash.generate_r1cs_constraints();
225229

226230
// Validate
231+
ownerValid.generate_r1cs_constraints();
227232
requireValidUntil.generate_r1cs_constraints();
228233
requireValidFee.generate_r1cs_constraints();
229234

packages/loopring_v3/circuit/Utils/Data.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ static void from_json(const json &j, Withdrawal &withdrawal)
381381
class AccountUpdateTx
382382
{
383383
public:
384+
ethsnarks::FieldT owner;
384385
ethsnarks::FieldT accountID;
385386
ethsnarks::FieldT publicKeyX;
386387
ethsnarks::FieldT publicKeyY;
@@ -393,6 +394,7 @@ class AccountUpdateTx
393394

394395
static void from_json(const json &j, AccountUpdateTx &update)
395396
{
397+
update.owner = ethsnarks::FieldT(j.at("owner").get<std::string>().c_str());
396398
update.accountID = ethsnarks::FieldT(j.at("accountID"));
397399
update.publicKeyX = ethsnarks::FieldT(j["publicKeyX"].get<std::string>().c_str());
398400
update.publicKeyY = ethsnarks::FieldT(j["publicKeyY"].get<std::string>().c_str());
@@ -557,6 +559,8 @@ static void from_json(const json &j, UniversalTransaction &transaction)
557559
// Patch some of the dummy tx's so they are valid against the current state
558560
// Deposit
559561
transaction.deposit.owner = transaction.witness.accountUpdate_A.before.owner;
562+
// AccountUpdate
563+
transaction.accountUpdate.owner = transaction.witness.accountUpdate_A.before.owner;
560564
// Transfer
561565
transaction.transfer.to = transaction.witness.accountUpdate_B.before.owner;
562566
transaction.transfer.payerTo = transaction.witness.accountUpdate_B.before.owner;

packages/loopring_v3/operator/state.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ def executeTransaction(self, context, txInput):
684684
newState.TXV_ACCOUNT_A_ADDRESS = txInput.accountID
685685
accountA = self.getAccount(newState.TXV_ACCOUNT_A_ADDRESS)
686686

687+
newState.TXV_ACCOUNT_A_OWNER = txInput.owner
687688
newState.TXV_ACCOUNT_A_PUBKEY_X = txInput.publicKeyX
688689
newState.TXV_ACCOUNT_A_PUBKEY_Y = txInput.publicKeyY
689690
newState.TXV_ACCOUNT_A_NONCE = 1

packages/loopring_v3/test/simulator.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,7 @@ export class Balance {
5757
this.storage = {};
5858
}
5959

60-
public init(
61-
balance: BN,
62-
weightAMM: BN,
63-
storage: { [key: number]: Storage }
64-
) {
60+
public init(balance: BN, weightAMM: BN, storage: { [key: number]: Storage }) {
6561
this.balance = new BN(balance.toString(10));
6662
this.weightAMM = new BN(weightAMM.toString(10));
6763
this.storage = storage;
@@ -178,9 +174,7 @@ export class Simulator {
178174
const jBalance = jAccount._balancesLeafs[balanceKey];
179175

180176
const storage: { [key: number]: Storage } = {};
181-
const storageKeys: string[] = Object.keys(
182-
jBalance._storageLeafs
183-
);
177+
const storageKeys: string[] = Object.keys(jBalance._storageLeafs);
184178
for (const storageKey of storageKeys) {
185179
const jStorage = jBalance._storageLeafs[storageKey];
186180
storage[Number(storageKey)] = {
@@ -322,7 +316,11 @@ export class Simulator {
322316
"pubKeyY does not match"
323317
);
324318
assert.equal(accountA.nonce, accountB.nonce, "nonce does not match");
325-
assert.equal(accountA.feeBipsAMM, accountB.feeBipsAMM, "feeBipsAMM does not match");
319+
assert.equal(
320+
accountA.feeBipsAMM,
321+
accountB.feeBipsAMM,
322+
"feeBipsAMM does not match"
323+
);
326324
}
327325

328326
public static executeBlock(
@@ -504,6 +502,7 @@ export class Simulator {
504502
update: AccountUpdate
505503
) {
506504
const account = state.getAccount(update.accountID);
505+
account.owner = update.owner;
507506
account.publicKeyX = update.publicKeyX;
508507
account.publicKeyY = update.publicKeyY;
509508
account.nonce++;
@@ -554,7 +553,9 @@ export class Simulator {
554553
from.getBalance(transfer.feeTokenID).balance.isub(transfer.fee);
555554

556555
// Nonce
557-
const storage = from.getBalance(transfer.tokenID).getStorage(transfer.storageID);
556+
const storage = from
557+
.getBalance(transfer.tokenID)
558+
.getStorage(transfer.storageID);
558559
storage.data = new BN(1);
559560
storage.storageID = transfer.storageID;
560561

@@ -581,19 +582,17 @@ export class Simulator {
581582
amount = new BN(0);
582583
}
583584
account.getBalance(withdrawal.tokenID).balance.isub(amount);
584-
account
585-
.getBalance(withdrawal.feeTokenID)
586-
.balance.isub(withdrawal.fee);
585+
account.getBalance(withdrawal.feeTokenID).balance.isub(withdrawal.fee);
587586

588587
const operator = state.getAccount(block.operatorAccountID);
589-
operator
590-
.getBalance(withdrawal.feeTokenID)
591-
.balance.iadd(withdrawal.fee);
588+
operator.getBalance(withdrawal.feeTokenID).balance.iadd(withdrawal.fee);
592589

593590
if (withdrawal.type === 0 || withdrawal.type === 1) {
594591
// Nonce
595592
const storageSlot = withdrawal.storageID % Constants.NUM_STORAGE_SLOTS;
596-
const storage = account.getBalance(withdrawal.tokenID).getStorage(storageSlot);
593+
const storage = account
594+
.getBalance(withdrawal.tokenID)
595+
.getStorage(storageSlot);
597596
storage.storageID = withdrawal.storageID;
598597
storage.data = new BN(1);
599598
}
@@ -907,7 +906,8 @@ export class Simulator {
907906
.isub(s.feeA);
908907

909908
const tradeHistoryA = accountA.getBalance(tokenA).getStorage(storageIdA);
910-
tradeHistoryA.data = storageIdA > tradeHistoryA.storageID ? new BN(0) : tradeHistoryA.data;
909+
tradeHistoryA.data =
910+
storageIdA > tradeHistoryA.storageID ? new BN(0) : tradeHistoryA.data;
911911
tradeHistoryA.data.iadd(fillAmountBorSA ? s.fillBA : s.fillSA);
912912
tradeHistoryA.storageID = storageIdA;
913913
}
@@ -921,7 +921,8 @@ export class Simulator {
921921
.isub(s.feeB);
922922

923923
const tradeHistoryB = accountB.getBalance(tokenB).getStorage(storageIdB);
924-
tradeHistoryB.data = storageIdB > tradeHistoryB.storageID ? new BN(0) : tradeHistoryB.data;
924+
tradeHistoryB.data =
925+
storageIdB > tradeHistoryB.storageID ? new BN(0) : tradeHistoryB.data;
925926
tradeHistoryB.data.iadd(fillAmountBorSB ? s.fillBB : s.fillSB);
926927
tradeHistoryB.storageID = storageIdB;
927928
}
@@ -1074,8 +1075,10 @@ export class Simulator {
10741075
.getBalance(order.tokenIdS)
10751076
.getStorage(order.storageID);
10761077
// Trade history trimming
1077-
const leafStorageID = tradeHistory.storageID === 0 ? storageSlot : tradeHistory.storageID;
1078-
const filled = leafStorageID === order.storageID ? tradeHistory.data : new BN(0);
1078+
const leafStorageID =
1079+
tradeHistory.storageID === 0 ? storageSlot : tradeHistory.storageID;
1080+
const filled =
1081+
leafStorageID === order.storageID ? tradeHistory.data : new BN(0);
10791082
return filled;
10801083
}
10811084

packages/loopring_v3/test/testExchangeAccounts.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,31 @@ contract("Exchange", (accounts: string[]) => {
4040
describe("Accounts", function() {
4141
this.timeout(0);
4242

43+
it("Should be able to create an account", async () => {
44+
await createExchange();
45+
46+
const token = ctx.getTokenAddress("LRC");
47+
const fee = new BN(0);
48+
49+
// Create the account
50+
let newKeyPair = ctx.getKeyPairEDDSA();
51+
await ctx.requestAccountUpdate(ownerA, token, fee, newKeyPair, {
52+
authMethod: AuthMethod.ECDSA
53+
});
54+
55+
// Submit
56+
await ctx.submitTransactions();
57+
await ctx.submitPendingBlocks();
58+
59+
// Try to create the account with EdDSA
60+
await ctx.requestAccountUpdate(ownerB, token, fee, newKeyPair, {
61+
authMethod: AuthMethod.EDDSA
62+
});
63+
64+
// Submit
65+
await expectThrow(ctx.submitTransactions(), "invalid block");
66+
});
67+
4368
it("Should be able to update an account", async () => {
4469
await createExchange();
4570

packages/loopring_v3/test/testExchangeUtil.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,7 +1402,20 @@ export class ExchangeTestUtil {
14021402
}
14031403
const feeTokenID = this.tokenAddressToIDMap.get(feeToken);
14041404

1405-
const account = this.findAccount(owner);
1405+
let isNewAccount = false;
1406+
let account = this.findAccount(owner);
1407+
if (account === undefined) {
1408+
account = {
1409+
accountID: this.accounts[this.exchangeId].length,
1410+
owner: owner,
1411+
publicKeyX: "0",
1412+
publicKeyY: "0",
1413+
secretKey: "0",
1414+
nonce: 0
1415+
};
1416+
this.accounts[this.exchangeId].push(account);
1417+
isNewAccount = true;
1418+
}
14061419

14071420
const accountUpdate: AccountUpdate = {
14081421
txType: "AccountUpdate",
@@ -1422,7 +1435,12 @@ export class ExchangeTestUtil {
14221435

14231436
// Sign the public key update
14241437
if (authMethod === AuthMethod.EDDSA) {
1425-
accountUpdate.signature = AccountUpdateUtils.sign(account, accountUpdate);
1438+
// New accounts should not be able to set keys with EDDSA.
1439+
// Try to sign with the keys we're setting (which shouldn't work).
1440+
accountUpdate.signature = AccountUpdateUtils.sign(
1441+
isNewAccount ? keyPair : account,
1442+
accountUpdate
1443+
);
14261444
} else if (authMethod === AuthMethod.ECDSA) {
14271445
const hash = AccountUpdateUtils.getHash(
14281446
accountUpdate,

0 commit comments

Comments
 (0)