Skip to content

Commit 8a75d1d

Browse files
authored
[protocol 3.6] Add maxFee to account update (#1770)
1 parent c352fdf commit 8a75d1d

File tree

6 files changed

+77
-18
lines changed

6 files changed

+77
-18
lines changed

packages/loopring_v3/circuit/Circuits/AccountUpdateCircuit.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
2929
VariableT publicKeyY;
3030
DualVariableGadget feeTokenID;
3131
DualVariableGadget fee;
32+
DualVariableGadget maxFee;
3233
DualVariableGadget type;
3334

3435
// Signature
3536
Poseidon_gadget_T<9, 1, 6, 53, 8, 1> hash;
3637

3738
// Validate
3839
RequireLtGadget requireValidUntil;
40+
RequireLeqGadget requireValidFee;
3941

4042
// Type
4143
IsNonZero isConditional;
@@ -73,6 +75,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
7375
publicKeyY(make_variable(pb, FMT(prefix, ".publicKeyY"))),
7476
feeTokenID(pb, NUM_BITS_TOKEN, FMT(prefix, ".feeTokenID")),
7577
fee(pb, NUM_BITS_AMOUNT, FMT(prefix, ".fee")),
78+
maxFee(pb, NUM_BITS_AMOUNT, FMT(prefix, ".maxFee")),
7679
type(pb, NUM_BITS_TYPE, FMT(prefix, ".type")),
7780

7881
// Signature
@@ -82,7 +85,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
8285
{state.exchange,
8386
accountID.packed,
8487
feeTokenID.packed,
85-
fee.packed,
88+
maxFee.packed,
8689
publicKeyX,
8790
publicKeyY,
8891
validUntil.packed,
@@ -96,6 +99,7 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
9699
validUntil.packed,
97100
NUM_BITS_TIMESTAMP,
98101
FMT(prefix, ".requireValidUntil")),
102+
requireValidFee(pb, fee.packed, maxFee.packed, NUM_BITS_AMOUNT, FMT(prefix, ".requireValidFee")),
99103

100104
// Type
101105
isConditional(pb, type.packed, ".isConditional"),
@@ -172,13 +176,15 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
172176
pb.val(publicKeyY) = update.publicKeyY;
173177
feeTokenID.generate_r1cs_witness(pb, update.feeTokenID);
174178
fee.generate_r1cs_witness(pb, update.fee);
179+
maxFee.generate_r1cs_witness(pb, update.maxFee);
175180
type.generate_r1cs_witness(pb, update.type);
176181

177182
// Signature
178183
hash.generate_r1cs_witness();
179184

180185
// Validate
181186
requireValidUntil.generate_r1cs_witness();
187+
requireValidFee.generate_r1cs_witness();
182188

183189
// Type
184190
isConditional.generate_r1cs_witness();
@@ -211,13 +217,15 @@ class AccountUpdateCircuit : public BaseTransactionCircuit
211217
nonce.generate_r1cs_constraints();
212218
feeTokenID.generate_r1cs_constraints(true);
213219
fee.generate_r1cs_constraints(true);
220+
maxFee.generate_r1cs_constraints(true);
214221
type.generate_r1cs_constraints(true);
215222

216223
// Signature
217224
hash.generate_r1cs_constraints();
218225

219226
// Validate
220227
requireValidUntil.generate_r1cs_constraints();
228+
requireValidFee.generate_r1cs_constraints();
221229

222230
// Type
223231
isConditional.generate_r1cs_constraints();

packages/loopring_v3/circuit/Utils/Data.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ static auto dummyAccountUpdate = R"({
9191
"publicKeyY": "0",
9292
"feeTokenID": 0,
9393
"fee": "0",
94+
"maxFee": "0",
9495
"type": 0
9596
})"_json;
9697

@@ -384,6 +385,7 @@ class AccountUpdateTx
384385
ethsnarks::FieldT publicKeyY;
385386
ethsnarks::FieldT feeTokenID;
386387
ethsnarks::FieldT fee;
388+
ethsnarks::FieldT maxFee;
387389
ethsnarks::FieldT validUntil;
388390
ethsnarks::FieldT type;
389391
};
@@ -395,6 +397,7 @@ static void from_json(const json &j, AccountUpdateTx &update)
395397
update.publicKeyY = ethsnarks::FieldT(j["publicKeyY"].get<std::string>().c_str());
396398
update.feeTokenID = ethsnarks::FieldT(j.at("feeTokenID"));
397399
update.fee = ethsnarks::FieldT(j["fee"].get<std::string>().c_str());
400+
update.maxFee = ethsnarks::FieldT(j["maxFee"].get<std::string>().c_str());
398401
update.validUntil = ethsnarks::FieldT(j.at("validUntil"));
399402
update.type = ethsnarks::FieldT(j.at("type"));
400403
}

packages/loopring_v3/operator/create_block.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ def accountUpdateFromJSON(jUpdate):
121121
update.publicKeyY = str(jUpdate["publicKeyY"])
122122
update.feeTokenID = int(jUpdate["feeTokenID"])
123123
update.fee = str(jUpdate["fee"])
124+
update.maxFee = str(jUpdate["maxFee"])
124125
update.type = int(jUpdate["type"])
125126
update.signature = None
126127
if "signature" in jUpdate:

packages/loopring_v3/test/testExchangeAccounts.ts

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,13 @@ contract("Exchange", (accounts: string[]) => {
1515
let ownerD: string;
1616

1717
const createExchange = async (setupTestState: boolean = true) => {
18-
exchangeID = await ctx.createExchange(
19-
ctx.testContext.stateOwners[0],
20-
{setupTestState}
21-
);
18+
exchangeID = await ctx.createExchange(ctx.testContext.stateOwners[0], {
19+
setupTestState
20+
});
2221
operatorAccountID = await ctx.getActiveOperator(exchangeID);
2322
operator = ctx.getAccount(operatorAccountID).owner;
2423
};
2524

26-
2725
before(async () => {
2826
ctx = new ExchangeTestUtil();
2927
await ctx.initialize(accounts);
@@ -53,35 +51,80 @@ contract("Exchange", (accounts: string[]) => {
5351
await ctx.deposit(ownerA, ownerA, token, balance);
5452

5553
// Send some funds over to the new account
56-
await ctx.transfer(ownerA, ownerB, token, fee.mul(new BN(10)), token, fee, {transferToNew: true});
54+
await ctx.transfer(
55+
ownerA,
56+
ownerB,
57+
token,
58+
fee.mul(new BN(10)),
59+
token,
60+
fee,
61+
{ transferToNew: true }
62+
);
5763

5864
// Update the key pair of the new account
5965
let newKeyPair = ctx.getKeyPairEDDSA();
60-
await ctx.requestAccountUpdate(ownerB, token, fee, newKeyPair, {authMethod: AuthMethod.ECDSA});
66+
await ctx.requestAccountUpdate(ownerB, token, fee, newKeyPair, {
67+
authMethod: AuthMethod.ECDSA
68+
});
6169

6270
// Update the key pair of the new account again to disable EdDSA signatures
63-
await ctx.requestAccountUpdate(ownerB, token, fee, ctx.getZeroKeyPairEDDSA());
71+
await ctx.requestAccountUpdate(
72+
ownerB,
73+
token,
74+
fee,
75+
ctx.getZeroKeyPairEDDSA(),
76+
{ maxFee: fee.mul(new BN(3)) }
77+
);
6478

6579
// Transfer some funds using an ECDSA signature
66-
await ctx.transfer(ownerB, ownerA, token, fee.mul(new BN(10)), token, fee, {authMethod: AuthMethod.ECDSA});
80+
await ctx.transfer(
81+
ownerB,
82+
ownerA,
83+
token,
84+
fee.mul(new BN(10)),
85+
token,
86+
fee,
87+
{ authMethod: AuthMethod.ECDSA }
88+
);
6789

6890
// Enable EdDSA signature again
6991
newKeyPair = ctx.getKeyPairEDDSA();
70-
await ctx.requestAccountUpdate(ownerB, token, fee, newKeyPair, {authMethod: AuthMethod.ECDSA});
92+
await ctx.requestAccountUpdate(ownerB, token, fee, newKeyPair, {
93+
authMethod: AuthMethod.ECDSA
94+
});
7195

7296
// Submit
7397
await ctx.submitTransactions();
7498
await ctx.submitPendingBlocks();
7599

76100
// Try to update the account without approval
77-
await ctx.requestAccountUpdate(ownerB, token, fee, newKeyPair, {authMethod: AuthMethod.NONE});
101+
await ctx.requestAccountUpdate(ownerB, token, fee, newKeyPair, {
102+
authMethod: AuthMethod.NONE
103+
});
78104

79105
// Submit
80106
await ctx.submitTransactions();
81-
await expectThrow(
82-
ctx.submitPendingBlocks(),
83-
"TX_NOT_APPROVED"
84-
);
107+
await expectThrow(ctx.submitPendingBlocks(), "TX_NOT_APPROVED");
108+
});
109+
110+
it("Should not be able to update an account with fee > maxFee", async () => {
111+
await createExchange();
112+
113+
const balance = new BN(web3.utils.toWei("5484.24", "ether"));
114+
const token = ctx.getTokenAddress("LRC");
115+
const fee = ctx.getRandomFee();
116+
117+
// Fund the payer
118+
await ctx.deposit(ownerA, ownerA, token, balance);
119+
120+
// Update the key pair of the new account
121+
let newKeyPair = ctx.getKeyPairEDDSA();
122+
await ctx.requestAccountUpdate(ownerA, token, fee, newKeyPair, {
123+
maxFee: fee.div(new BN(2))
124+
});
125+
126+
// Submit
127+
await expectThrow(ctx.submitTransactions(), "invalid block");
85128
});
86129
});
87130
});

packages/loopring_v3/test/testExchangeUtil.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export interface WithdrawOptions {
128128
export interface AccountUpdateOptions {
129129
authMethod?: AuthMethod;
130130
validUntil?: number;
131+
maxFee?: BN;
131132
}
132133

133134
export interface AmmUpdateOptions {
@@ -206,7 +207,7 @@ export namespace AccountUpdateUtils {
206207
update.exchange,
207208
update.accountID,
208209
update.feeTokenID,
209-
update.fee,
210+
update.maxFee,
210211
update.publicKeyX,
211212
update.publicKeyY,
212213
update.validUntil,
@@ -1356,6 +1357,7 @@ export class ExchangeTestUtil {
13561357
options.authMethod !== undefined ? options.authMethod : AuthMethod.EDDSA;
13571358
const validUntil =
13581359
options.validUntil !== undefined ? options.validUntil : 0xffffffff;
1360+
const maxFee = options.maxFee !== undefined ? options.maxFee : fee;
13591361

13601362
// Type
13611363
let type = 0;
@@ -1381,7 +1383,8 @@ export class ExchangeTestUtil {
13811383
publicKeyX: keyPair.publicKeyX,
13821384
publicKeyY: keyPair.publicKeyY,
13831385
feeTokenID,
1384-
fee
1386+
fee,
1387+
maxFee
13851388
};
13861389

13871390
// Sign the public key update

packages/loopring_v3/test/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export interface AccountUpdate {
9696
publicKeyY: string;
9797
feeTokenID: number;
9898
fee: BN;
99+
maxFee: BN;
99100

100101
signature?: Signature;
101102
onchainSignature?: any;

0 commit comments

Comments
 (0)