Skip to content

Commit bc644c5

Browse files
authored
Merge pull request #57 from firstbatchxyz/erhant/batch
Fix `getMany` and add `putMany`
2 parents e611ccd + 574e5a1 commit bc644c5

20 files changed

+445
-34
lines changed

src/base/base.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,10 @@ export class Base<M extends ContractMode> {
7878
}
7979

8080
/**
81-
* A typed wrapper around `viewState` followed with a repsonse type check. If
82-
* response type is not `ok`, it will throw an error with an optional prefix.
81+
* A typed wrapper around `viewState` followed with a repsonse type check.
82+
* If response type is not `ok`, it will throw an error.
8383
* @param input input in the form of `{function, value}`
84-
* @param errorPrefix optional prefix for the error message
85-
* @returns
84+
* @returns interaction result
8685
*/
8786
async safeReadInteraction<I extends ContractInputGeneric, V>(input: I) {
8887
const response = await this.viewState<I, V>(input);

src/base/sdk.ts

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import type {
55
ContractMode,
66
ContractState,
77
GetInput,
8+
GetManyInput,
89
GetKVMapInput,
910
GetKeysInput,
1011
PutInput,
12+
PutManyInput,
1113
RemoveInput,
1214
UpdateInput,
1315
} from '../contracts/types';
@@ -43,32 +45,35 @@ export class SDK<V = unknown, M extends ContractMode = ContractMode> {
4345
return this.base.signer;
4446
}
4547

46-
/** Returns the latest contract state.
48+
/**
49+
* Returns the latest contract state.
4750
*
4851
* For a more fine-grained state data, use `base.readState()`.
4952
*
53+
* @returns contract state object
5054
*/
5155
async getState(): Promise<ContractState<M>> {
5256
return await this.base.readState().then(s => s.cachedValue.state);
5357
}
5458

55-
/** Gets the values at the given keys as an array. */
56-
async getMany(keys: string[]): Promise<(V | null)[]> {
57-
return await Promise.all(keys.map(key => this.get(key)));
58-
}
59-
6059
/**
6160
* Alternative method of getting key values. Uses the underlying `getStorageValues`
6261
* function, returns a Map instead of an array.
62+
*
63+
* @param keys an array of keys
64+
* @returns `SortKeyCacheResult` of a key-value `Map`
6365
*/
64-
async getStorageValues(keys: string[]) {
66+
async getStorageValues(keys: string[]): Promise<SortKeyCacheResult<Map<string, V | null>>> {
6567
return (await this.contract.getStorageValues(keys)) as SortKeyCacheResult<Map<string, V | null>>;
6668
}
6769

6870
/**
6971
* Returns keys with respect to a range option.
7072
*
7173
* If no option is provided, it will get all keys.
74+
*
75+
* @param options optional range
76+
* @returns an array of keys
7277
*/
7378
async getKeys(options?: SortKeyCacheRangeOptions): Promise<string[]> {
7479
return await this.base.safeReadInteraction<GetKeysInput, string[]>({
@@ -79,14 +84,21 @@ export class SDK<V = unknown, M extends ContractMode = ContractMode> {
7984
});
8085
}
8186

82-
/** Returns all keys in the database. */
87+
/**
88+
* Returns all keys in the database.
89+
*
90+
* @returns an array of all keys
91+
*/
8392
async getAllKeys(): Promise<string[]> {
8493
return this.getKeys();
8594
}
8695

8796
/**
8897
* Returns a mapping of keys and values with respect to a range option.
8998
* If no option is provided, all values are returned.
99+
*
100+
* @param options optional range
101+
* @returns a key-value `Map`
90102
*/
91103
async getKVMap(options?: SortKeyCacheRangeOptions): Promise<Map<string, V>> {
92104
return await this.base.safeReadInteraction<GetKVMapInput, Map<string, V>>({
@@ -111,8 +123,30 @@ export class SDK<V = unknown, M extends ContractMode = ContractMode> {
111123
});
112124
}
113125

126+
/**
127+
* Gets the values at the given keys as an array.
128+
*
129+
* If a value does not exist, it is returned as `null`.
130+
*
131+
* Note that the transaction limit may become a problem for too many keys.
132+
*
133+
* @param keys an array of keys
134+
* @returns an array of corresponding values
135+
*/
136+
async getMany(keys: string[]): Promise<(V | null)[]> {
137+
return await this.base.safeReadInteraction<GetManyInput, (V | null)[]>({
138+
function: 'getMany',
139+
value: {
140+
keys,
141+
},
142+
});
143+
}
144+
114145
/**
115146
* Inserts the given value into database.
147+
*
148+
* There must not be a value at the given key.
149+
*
116150
* @param key the key of the value to be inserted
117151
* @param value the value to be inserted
118152
*/
@@ -126,6 +160,24 @@ export class SDK<V = unknown, M extends ContractMode = ContractMode> {
126160
});
127161
}
128162

163+
/**
164+
* Inserts an array of value into database.
165+
*
166+
* There must not be a value at the given key.
167+
*
168+
* @param keys the keys of the values to be inserted
169+
* @param values the values to be inserted
170+
*/
171+
async putMany(keys: string[], values: V[]): Promise<void> {
172+
await this.base.dryWriteInteraction<PutManyInput<V>>({
173+
function: 'putMany',
174+
value: {
175+
keys,
176+
values,
177+
},
178+
});
179+
}
180+
129181
/**
130182
* Updates the value of given key.
131183
* @param key key of the value to be updated

src/bin/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ yargs(hideBin(process.argv))
5959
let result;
6060
if (args.sourceTxId) {
6161
result = await deployFromSrc(wallet, warp, state, args.sourceTxId);
62-
console.log(`${args.name} contract deployed.`);
62+
console.log(`${args.name} contract deployed from source ${args.sourceTxId}.`);
6363
} else {
6464
const code = prepareCode(args.name);
6565
result = await deploy(wallet, warp, state, code);
66-
console.log(`${args.name} contract deployed from source ${args.sourceTxId}.`);
66+
console.log(`${args.name} contract deployed.`);
6767
}
6868
console.log(result);
6969
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
2+
// src/contracts/errors/index.ts
3+
var KeyExistsError = new ContractError("Key already exists.");
4+
var KeyNotExistsError = new ContractError("Key does not exist.");
5+
var CantEvolveError = new ContractError("Evolving is disabled.");
6+
var NoVerificationKeyError = new ContractError("No verification key.");
7+
var UnknownProtocolError = new ContractError("Unknown protocol.");
8+
var NotWhitelistedError = new ContractError("Not whitelisted.");
9+
var InvalidProofError = new ContractError("Invalid proof.");
10+
var ExpectedProofError = new ContractError("Expected a proof.");
11+
var NullValueError = new ContractError("Value cant be null, use remove instead.");
12+
var NotOwnerError = new ContractError("Not contract owner.");
13+
var InvalidFunctionError = new ContractError("Invalid function.");
14+
15+
// src/contracts/utils/index.ts
16+
var verifyProof = async (proof, psignals, verificationKey) => {
17+
if (!verificationKey) {
18+
throw NoVerificationKeyError;
19+
}
20+
if (verificationKey.protocol !== "groth16" && verificationKey.protocol !== "plonk") {
21+
throw UnknownProtocolError;
22+
}
23+
return await SmartWeave.extensions[verificationKey.protocol].verify(verificationKey, psignals, proof);
24+
};
25+
var hashToGroup = (value) => {
26+
if (value) {
27+
return BigInt(SmartWeave.extensions.ethers.utils.ripemd160(Buffer.from(JSON.stringify(value))));
28+
} else {
29+
return BigInt(0);
30+
}
31+
};
32+
33+
// src/contracts/modifiers/index.ts
34+
var onlyOwner = (caller, input, state) => {
35+
if (caller !== state.owner) {
36+
throw NotOwnerError;
37+
}
38+
return input;
39+
};
40+
var onlyNonNullValue = (_, input) => {
41+
if (input.value === null) {
42+
throw NullValueError;
43+
}
44+
return input;
45+
};
46+
var onlyWhitelisted = (list) => {
47+
return (caller, input, state) => {
48+
if (!state.isWhitelistRequired[list]) {
49+
return input;
50+
}
51+
if (!state.whitelists[list][caller]) {
52+
throw NotWhitelistedError;
53+
}
54+
return input;
55+
};
56+
};
57+
var onlyProofVerified = (proofName, prepareInputs) => {
58+
return async (caller, input, state) => {
59+
if (!state.isProofRequired[proofName]) {
60+
return input;
61+
}
62+
if (!input.proof) {
63+
throw ExpectedProofError;
64+
}
65+
const ok = await verifyProof(
66+
input.proof,
67+
await prepareInputs(caller, input, state),
68+
state.verificationKeys[proofName]
69+
);
70+
if (!ok) {
71+
throw InvalidProofError;
72+
}
73+
return input;
74+
};
75+
};
76+
async function apply(caller, input, state, ...modifiers) {
77+
for (const modifier of modifiers) {
78+
input = await modifier(caller, input, state);
79+
}
80+
return input;
81+
}
82+
83+
// src/contracts/hollowdb-batch.contract.ts
84+
var handle = async (state, action) => {
85+
const { caller, input } = action;
86+
switch (input.function) {
87+
case "get": {
88+
const { key } = await apply(caller, input.value, state);
89+
return { result: await SmartWeave.kv.get(key) };
90+
}
91+
case "getMulti": {
92+
const { keys } = await apply(caller, input.value, state);
93+
return { result: await Promise.all(keys.map((key) => SmartWeave.kv.get(key))) };
94+
}
95+
case "getKeys": {
96+
const { options } = await apply(caller, input.value, state);
97+
return { result: await SmartWeave.kv.keys(options) };
98+
}
99+
case "getKVMap": {
100+
const { options } = await apply(caller, input.value, state);
101+
return { result: await SmartWeave.kv.kvMap(options) };
102+
}
103+
case "put": {
104+
const { key, value } = await apply(caller, input.value, state, onlyNonNullValue, onlyWhitelisted("put"));
105+
if (await SmartWeave.kv.get(key) !== null) {
106+
throw KeyExistsError;
107+
}
108+
await SmartWeave.kv.put(key, value);
109+
return { state };
110+
}
111+
case "putMulti": {
112+
const { keys, values } = await apply(caller, input.value, state, onlyWhitelisted("put"));
113+
if (values.some((val) => val === null)) {
114+
throw new ContractError("Key and value counts mismatch");
115+
}
116+
if (values.some((val) => val === null)) {
117+
throw NullValueError;
118+
}
119+
const existing = await Promise.all(keys.map((key) => SmartWeave.kv.get(key)));
120+
if (existing.some((val) => val !== null)) {
121+
throw KeyExistsError;
122+
}
123+
await Promise.all(keys.map((key, i) => SmartWeave.kv.put(key, values[i])));
124+
return { state };
125+
}
126+
case "update": {
127+
const { key, value } = await apply(
128+
caller,
129+
input.value,
130+
state,
131+
onlyNonNullValue,
132+
onlyWhitelisted("update"),
133+
onlyProofVerified("auth", async (_, input2) => {
134+
const oldValue = await SmartWeave.kv.get(input2.key);
135+
return [hashToGroup(oldValue), hashToGroup(input2.value), BigInt(input2.key)];
136+
})
137+
);
138+
await SmartWeave.kv.put(key, value);
139+
return { state };
140+
}
141+
case "remove": {
142+
const { key } = await apply(
143+
caller,
144+
input.value,
145+
state,
146+
onlyWhitelisted("update"),
147+
onlyProofVerified("auth", async (_, input2) => {
148+
const oldValue = await SmartWeave.kv.get(input2.key);
149+
return [hashToGroup(oldValue), BigInt(0), BigInt(input2.key)];
150+
})
151+
);
152+
await SmartWeave.kv.del(key);
153+
return { state };
154+
}
155+
case "updateOwner": {
156+
const { newOwner } = await apply(caller, input.value, state, onlyOwner);
157+
state.owner = newOwner;
158+
return { state };
159+
}
160+
case "updateProofRequirement": {
161+
const { name, value } = await apply(caller, input.value, state, onlyOwner);
162+
state.isProofRequired[name] = value;
163+
return { state };
164+
}
165+
case "updateVerificationKey": {
166+
const { name, verificationKey } = await apply(caller, input.value, state, onlyOwner);
167+
state.verificationKeys[name] = verificationKey;
168+
return { state };
169+
}
170+
case "updateWhitelistRequirement": {
171+
const { name, value } = await apply(caller, input.value, state, onlyOwner);
172+
state.isWhitelistRequired[name] = value;
173+
return { state };
174+
}
175+
case "updateWhitelist": {
176+
const { add, remove, name } = await apply(caller, input.value, state, onlyOwner);
177+
add.forEach((user) => {
178+
state.whitelists[name][user] = true;
179+
});
180+
remove.forEach((user) => {
181+
delete state.whitelists[name][user];
182+
});
183+
return { state };
184+
}
185+
case "evolve": {
186+
const srcTxId = await apply(caller, input.value, state, onlyOwner);
187+
if (!state.canEvolve) {
188+
throw CantEvolveError;
189+
}
190+
state.evolve = srcTxId;
191+
return { state };
192+
}
193+
default:
194+
input;
195+
throw InvalidFunctionError;
196+
}
197+
};
198+

0 commit comments

Comments
 (0)