Skip to content

Commit

Permalink
Merge pull request #11 from Staketab/dev
Browse files Browse the repository at this point in the history
add Server Actions for handle name in home page
  • Loading branch information
gavrushoves authored Feb 28, 2024
2 parents d677859 + 75ba91b commit f4057c7
Show file tree
Hide file tree
Showing 69 changed files with 2,623 additions and 4,407 deletions.
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "mina-names-backend"]
path = mina-names-backend
url = https://github.com/Staketab/mina-names-backend
update = remote
205 changes: 205 additions & 0 deletions contracts/src/mapcontract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import {
Field,
state,
State,
method,
SmartContract,
DeployArgs,
Reducer,
Permissions,
Struct,
PublicKey,
UInt64,
UInt32,
Poseidon,
Signature,
} from "o1js";

import { Storage } from "./storage";
import { MapUpdateProof } from "./update";

export const BATCH_SIZE = 256;

export class MapElement extends Struct({
name: Field,
address: PublicKey,
addressHash: Field, // Poseidon hash of address.toFields()
hash: Field, // Poseidon hash of [name, ...address.toFields()]
storage: Storage,
}) {
toFields(): Field[] {
return [
this.name,
...this.address.toFields(),
this.addressHash,
this.hash,
...this.storage.toFields(),
];
}

static fromFields(fields: Field[]): MapElement {
return new MapElement({
name: fields[0],
address: PublicKey.fromFields(fields.slice(1, 3)),
addressHash: fields[3],
hash: fields[4],
storage: new Storage({ hashString: [fields[5], fields[6]] }),
});
}
}

export class ReducerState extends Struct({
count: Field,
hash: Field,
}) {
static assertEquals(a: ReducerState, b: ReducerState) {
a.count.assertEquals(b.count);
a.hash.assertEquals(b.hash);
}
}

export class MapContract extends SmartContract {
@state(Field) domain = State<Field>();
@state(Field) root = State<Field>();
@state(Field) count = State<Field>();
@state(Field) actionState = State<Field>();
@state(PublicKey) owner = State<PublicKey>();

deploy(args: DeployArgs) {
super.deploy(args);
this.account.permissions.set({
...Permissions.default(),
editState: Permissions.proof(),
});
}

reducer = Reducer({
actionType: MapElement,
});

events = {
add: MapElement,
update: MapElement,
reduce: ReducerState,
bulkUpdate: Field,
};

@method add(
name: Field,
address: PublicKey,
storage: Storage,
signature: Signature
) {
const addressHash = Poseidon.hash(address.toFields());
const hash = Poseidon.hash([name, ...address.toFields()]);
const element = new MapElement({
name,
address,
addressHash,
hash,
storage,
});
signature.verify(address, element.toFields()).assertEquals(true);
this.reducer.dispatch(element);
this.emitEvent("add", element);
}

@method update(
name: Field,
address: PublicKey,
storage: Storage,
signature: Signature
) {
const addressHash = Poseidon.hash(address.toFields());
const hash = Poseidon.hash([name, ...address.toFields()]);
const element = new MapElement({
name,
address,
addressHash,
hash,
storage,
});
signature.verify(address, element.toFields()).assertEquals(true);
this.emitEvent("update", element);
}

@method reduce(
startActionState: Field,
endActionState: Field,
reducerState: ReducerState,
proof: MapUpdateProof,
signature: Signature
) {
const owner = this.owner.getAndRequireEquals();
signature.verify(owner, proof.publicInput.toFields()).assertEquals(true);
proof.verify();
proof.publicInput.oldRoot.assertEquals(this.root.getAndRequireEquals());
proof.publicInput.hash.assertEquals(reducerState.hash);
proof.publicInput.count.assertEquals(reducerState.count.toFields()[0]);

const actionState = this.actionState.getAndRequireEquals();
actionState.assertEquals(startActionState);
const count = this.count.getAndRequireEquals();

const pendingActions = this.reducer.getActions({
fromActionState: actionState,
endActionState,
});

let elementsState: ReducerState = new ReducerState({
count: Field(0),
hash: Field(0),
});

const { state: newReducerState, actionState: newActionState } =
this.reducer.reduce(
pendingActions,
ReducerState,
(state: ReducerState, action: MapElement) => {
return new ReducerState({
count: state.count.add(Field(1)),
hash: state.hash.add(action.hash),
});
},
{
state: elementsState,
actionState: actionState,
},
{
maxTransactionsWithActions: BATCH_SIZE,
skipActionStatePrecondition: true,
}
);
ReducerState.assertEquals(newReducerState, reducerState);
this.count.set(count.add(newReducerState.count));
this.actionState.set(newActionState);
this.root.set(proof.publicInput.newRoot);
this.emitEvent("reduce", reducerState);
}

@method bulkUpdate(proof: MapUpdateProof, signature: Signature) {
const owner = this.owner.getAndRequireEquals();
signature.verify(owner, proof.publicInput.toFields()).assertEquals(true);
proof.verify();
proof.publicInput.oldRoot.assertEquals(this.root.getAndRequireEquals());

const count = this.count.getAndRequireEquals();
this.count.set(count.add(proof.publicInput.count));
this.root.set(proof.publicInput.newRoot);
this.emitEvent("bulkUpdate", proof.publicInput.count);
}

@method setOwner(newOwner: PublicKey, signature: Signature) {
const owner = this.owner.getAndRequireEquals();
signature.verify(owner, newOwner.toFields()).assertEquals(true);
this.owner.set(newOwner);
}

// TODO: remove after debugging
@method setRoot(root: Field, count: Field, signature: Signature) {
const owner = this.owner.getAndRequireEquals();
signature.verify(owner, [root, count]).assertEquals(true);
this.root.set(root);
this.count.set(count);
}
}
19 changes: 19 additions & 0 deletions contracts/src/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export { Storage };
import { Struct, Field } from "o1js";

/**
* Storage is the hash of the IPFS or Arweave storage where the metadata is written
* format of the IPFS hash string: i:...
* format of the Arweave hash string: a:...
* @property hashString The hash string of the storage
*/
class Storage extends Struct({
hashString: [Field, Field],
}) {
constructor(value: { hashString: [Field, Field] }) {
super(value);
}
toFields(): Field[] {
return this.hashString;
}
}
158 changes: 158 additions & 0 deletions contracts/src/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
export { MapUpdate, MapTransition, MapUpdateProof, MapUpdateData };
import {
Field,
SelfProof,
ZkProgram,
Struct,
MerkleMapWitness,
PublicKey,
Poseidon,
} from "o1js";

class MapUpdateData extends Struct({
oldRoot: Field,
newRoot: Field,
key: Field,
oldValue: Field,
newValue: Field,
witness: MerkleMapWitness,
}) {
toFields(): Field[] {
return [
this.oldRoot,
this.newRoot,
this.key,
this.oldValue,
this.newValue,
...this.witness.toFields(),
];
}

static fromFields(fields: Field[]): MapUpdateData {
return new MapUpdateData({
oldRoot: fields[0],
newRoot: fields[1],
key: fields[2],
oldValue: fields[3],
newValue: fields[4],
witness: MerkleMapWitness.fromFields(fields.slice(5)),
});
}
}

class MapTransition extends Struct({
oldRoot: Field,
newRoot: Field,
hash: Field, // sum of hashes of all the new keys and values of the Map
count: Field, // number of new keys in the Map
}) {
static accept(update: MapUpdateData, address: PublicKey) {
const [dataWitnessRootBefore, dataWitnessKey] =
update.witness.computeRootAndKey(update.oldValue);
update.oldRoot.assertEquals(dataWitnessRootBefore);
dataWitnessKey.assertEquals(update.key);

const [dataWitnessRootAfter, _] = update.witness.computeRootAndKey(
update.newValue
);
update.newRoot.assertEquals(dataWitnessRootAfter);
const addressHash = Poseidon.hash(address.toFields());
addressHash.assertEquals(update.newValue);

return new MapTransition({
oldRoot: update.oldRoot,
newRoot: update.newRoot,
hash: Poseidon.hash([update.key, ...address.toFields()]),
count: Field(1),
});
}

static reject(root: Field, key: Field, address: PublicKey) {
return new MapTransition({
oldRoot: root,
newRoot: root,
hash: Poseidon.hash([key, ...address.toFields()]),
count: Field(1),
});
}

static merge(transition1: MapTransition, transition2: MapTransition) {
transition1.newRoot.assertEquals(transition2.oldRoot);
return new MapTransition({
oldRoot: transition1.oldRoot,
newRoot: transition2.newRoot,
hash: transition1.hash.add(transition2.hash),
count: transition1.count.add(transition2.count),
});
}

static assertEquals(transition1: MapTransition, transition2: MapTransition) {
transition1.oldRoot.assertEquals(transition2.oldRoot);
transition1.newRoot.assertEquals(transition2.newRoot);
transition1.hash.assertEquals(transition2.hash);
transition1.count.assertEquals(transition2.count);
}

toFields(): Field[] {
return [this.oldRoot, this.newRoot, this.hash, this.count];
}

static fromFields(fields: Field[]): MapTransition {
return new MapTransition({
oldRoot: fields[0],
newRoot: fields[1],
hash: fields[2],
count: fields[3],
});
}
}

const MapUpdate = ZkProgram({
name: "MapUpdate",
publicInput: MapTransition,

methods: {
accept: {
privateInputs: [MapUpdateData, PublicKey],

method(state: MapTransition, update: MapUpdateData, address: PublicKey) {
const computedState = MapTransition.accept(update, address);
MapTransition.assertEquals(computedState, state);
},
},

reject: {
privateInputs: [Field, Field, PublicKey],

method(
state: MapTransition,
root: Field,
key: Field,
address: PublicKey
) {
const computedState = MapTransition.reject(root, key, address);
MapTransition.assertEquals(computedState, state);
},
},

merge: {
privateInputs: [SelfProof, SelfProof],

method(
newState: MapTransition,
proof1: SelfProof<MapTransition, void>,
proof2: SelfProof<MapTransition, void>
) {
proof1.verify();
proof2.verify();
const computedState = MapTransition.merge(
proof1.publicInput,
proof2.publicInput
);
MapTransition.assertEquals(computedState, newState);
},
},
},
});

class MapUpdateProof extends ZkProgram.Proof(MapUpdate) {}
Loading

0 comments on commit f4057c7

Please sign in to comment.