|
|
@@ -1,5 +1,5 @@
|
|
|
-/// Available functions in this contract.
|
|
|
-/// We only identify them with a single byte passed through the payload.
|
|
|
+/// Available functions for this contract.
|
|
|
+/// We identify them with the first byte passed in through the payload.
|
|
|
#[repr(u8)]
|
|
|
pub enum Function {
|
|
|
Transfer = 0x00,
|
|
|
@@ -14,171 +14,59 @@ impl From<u8> for Function {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/// This state is serialized and stored on-chain. See `src/blockchain/statestore.rs`.
|
|
|
+pub mod transfer;
|
|
|
+
|
|
|
+/// `State` represents this contract's state on-chain. The contract's
|
|
|
+/// entrypoint knows its own `ContractId` since it's passed in by the
|
|
|
+/// wasm runtime, so it knows what to request. Retrieval of the state
|
|
|
+/// from the blockchain is done with a host function called `lookup_state`.
|
|
|
+/// For more info, see:
|
|
|
+/// * `darkfi/src/blockchain/statestore.rs`
|
|
|
+/// * ~~~`darkfi/src/runtime/chain_state.rs`~~~
|
|
|
#[repr(C)]
|
|
|
-#[derive(SerialEncodable, SerialDecodable)]
|
|
|
+#[derive(Clone, SerialEncodable, SerialDecodable)]
|
|
|
pub struct State {
|
|
|
+ /// The Merkle tree of all coins used by this contract.
|
|
|
pub tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
|
|
|
+ /// List of all previous and current Merkle roots.
|
|
|
pub merkle_roots: Vec<MerkleNode>,
|
|
|
+ /// Published nullifiers that have been seen.
|
|
|
pub nullifiers: Vec<Nullifier>,
|
|
|
}
|
|
|
|
|
|
-pub struct Update {
|
|
|
- pub nullifiers: Vec<Nullifier>,
|
|
|
- pub coins: Vec<Coin>,
|
|
|
-}
|
|
|
-
|
|
|
impl State {
|
|
|
pub fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool {
|
|
|
self.merkle_roots.iter().any(|m| m == merkle_root)
|
|
|
}
|
|
|
|
|
|
- pub fn nullfier_exists(&self, nullifier: &Nullifier) -> bool {
|
|
|
+ pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
|
|
self.nullifiers.iter().any(|n| n == nullifier)
|
|
|
}
|
|
|
-
|
|
|
- pub fn update(&mut self, state_update: Update) {
|
|
|
- self.nullifiers.extend_from_slice(&state_update.nullifiers);
|
|
|
- for coin in state_update.coins {
|
|
|
- self.tree.append(&MerkleNode(coin.inner()));
|
|
|
- self.merkle_roots.push(self.tree.root(0).unwrap());
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-fn transfer(state: &mut State, payload: &[u8]) -> ContractResult {
|
|
|
- let tx: Transaction = deserialize(payload)?;
|
|
|
-
|
|
|
- // TODO: Clear inputs. Cashier + Faucet logic is bad and needs to be
|
|
|
- // solved in another way.
|
|
|
-
|
|
|
- // Nullifiers in the transaction
|
|
|
- let mut nullifiers = Vec::with_capacity(tx.inputs.len());
|
|
|
-
|
|
|
- msg!("Iterate inputs");
|
|
|
- for (i, input) in tx.inputs.iter().enumerate() {
|
|
|
- let merkle_root = *input.revealed.merkle_root;
|
|
|
- let spend_hook = *input.revealed.spend_hook;
|
|
|
- let nullifier = *input.revealed.nullifier;
|
|
|
-
|
|
|
- // The Merkle root is used to know whether this is a coin that
|
|
|
- // existed in a previous state.
|
|
|
- if !state.is_valid_merkle(&merkle_root) {
|
|
|
- msg!("Error: Invalid Merkle root (input {})", i);
|
|
|
- msg!("Root: {:?}", merkle_root);
|
|
|
- return Err(ContractError::Custom(30))
|
|
|
- }
|
|
|
-
|
|
|
- // Check the spend_hook is satisfied.
|
|
|
- // The spend_hook says a coin must invoke another contract function
|
|
|
- // when being spent. If the value is set, then we check the function
|
|
|
- // call exists.
|
|
|
- if spend_hook != pallas::Base::zero() {
|
|
|
- // spend_hook is set, so we enforce the rules.
|
|
|
- todo!();
|
|
|
- }
|
|
|
-
|
|
|
- // The nullifiers should not already exists - double-spend protection.
|
|
|
- if state.nullifier_exists(&nullifier) || nullifiers.contains(&nullifier) {
|
|
|
- msg!("Duplicate nullifier found (input {})", i);
|
|
|
- msg!("Nullifier: {:?}", nullifier);
|
|
|
- return Err(ContractError::Custom(31))
|
|
|
- }
|
|
|
-
|
|
|
- nullifiers.push(nullifier);
|
|
|
-
|
|
|
- // Verify transaction
|
|
|
- match self.verify() {
|
|
|
- Ok(()) => msg!("tx verified successfully"),
|
|
|
- Err(e) => {
|
|
|
- msg!("tx failed to verify");
|
|
|
- return Err(e)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let mut coins = Vec::with_capacity(tx.outputs.len());
|
|
|
- for output in tx.outputs {
|
|
|
- coins.push(output.revealed.coin);
|
|
|
- }
|
|
|
-
|
|
|
- let state_update = Update { nullifiers, coins };
|
|
|
- state.update(&state_update);
|
|
|
- apply_state(&serialize(&state))?;
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl Verification for Transaction {
|
|
|
- pub fn verify(&self) -> ContractResult {
|
|
|
- // Must have minimum 1 clear or anon input, and 1 output
|
|
|
- if self.clear_inputs.len() + self.inputs.len() == 0 {
|
|
|
- msg!("Error: Missing inputs in transaction");
|
|
|
- return Err(ContractError::Custom(32))
|
|
|
- }
|
|
|
-
|
|
|
- if self.outputs.is_empty() {
|
|
|
- msg!("Error: Missing outputs in transaction");
|
|
|
- return Err(ContractError::Custom(33))
|
|
|
- }
|
|
|
-
|
|
|
- // Accumulator for the value commitments
|
|
|
- let mut valcom_total = DrkValueCommit::identity();
|
|
|
-
|
|
|
- // Add values from the clear inputs
|
|
|
- for input in &self.clear_inputs {
|
|
|
- valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
|
|
|
- }
|
|
|
-
|
|
|
- // Add values from the inputs
|
|
|
- for input in &self.inputs {
|
|
|
- valcom_total += input.revealed.value_commit;
|
|
|
- }
|
|
|
-
|
|
|
- // Subtract values from the outputs
|
|
|
- for output in &self.outputs {
|
|
|
- valcom_total -= output.revealed.value_commit;
|
|
|
- }
|
|
|
-
|
|
|
- // If the accumulator is not back in its initial state,
|
|
|
- // there's a value mismatch.
|
|
|
- if valcom_total != DrkValueCommit::identity() {
|
|
|
- msg!("Error: Missing funds");
|
|
|
- return Err(ContractError::Custom(34))
|
|
|
- }
|
|
|
-
|
|
|
- // Verify that the token commitments match
|
|
|
- let token_commit_value = self.outputs[0].revealed.token_commit;
|
|
|
- let mut failed =
|
|
|
- self.inputs.iter().any(|input| input.revealed.token_commit != token_commit_value);
|
|
|
- failed = failed ||
|
|
|
- self.outputs.iter().any(|output| output.revealed.token_commit != token_commit_value);
|
|
|
- failed = failed ||
|
|
|
- self.clear_inputs.iter().any(|input| {
|
|
|
- pedersen_commitment_base(input.token_id, input.token_blind) != token_commit_value
|
|
|
- });
|
|
|
-
|
|
|
- if !failed {
|
|
|
- msg!("Error: Token ID mismatch");
|
|
|
- return Err(ContractError::Custom(35))
|
|
|
- }
|
|
|
-
|
|
|
- Ok(())
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
#[cfg(not(feature = "no-entrypoint"))]
|
|
|
entrypoint!(process_instruction);
|
|
|
fn process_instruction(contract_id: &ContractId, ix: &[u8]) -> ContractResult {
|
|
|
- // Using the `contract_id` (fed by the wasm runtime), we find our state in
|
|
|
- // the sled database, and try to deserialize it into the `State` struct that
|
|
|
- // is defined in this smart contract.
|
|
|
- // TODO: FIXME: The deserialization needs to be partial, because in the ledger
|
|
|
- // the smart contract deployer is supposed to allocate the space for this data
|
|
|
- // and whatever is unused should be zeroes.
|
|
|
+ // This is the entrypoint function of the smart contract which gets executed
|
|
|
+ // by the wasm runtime. The `contract_id` passed in is used to lookup the
|
|
|
+ // current state from the ledger using the `lookup_state` function.
|
|
|
+ // `ix` is an arbitrary payload fed into the contract. In this case, the
|
|
|
+ // first byte of the payload is a pointer to a function we with to run, and
|
|
|
+ // the remainter is a serialized `Transaction` object we'll try to deserialize
|
|
|
+ // and work with.
|
|
|
let mut state: State = deserialize(&lookup_state(contract_id)?)?;
|
|
|
|
|
|
match Function::from(ix[0]) {
|
|
|
- Function::Transfer => transfer(&mut state, &ix[1..])?,
|
|
|
+ Function::Transfer => {
|
|
|
+ let transaction = deserialize(&ix[1..])?;
|
|
|
+ transfer::exec(&mut state, transaction)?;
|
|
|
+ // If `transfer` succeeded, `state` will contain the updated state, so
|
|
|
+ // we can change it in the VM environment which is accessible by the
|
|
|
+ // host. Then if everything else outside of the wasm execution is
|
|
|
+ // valid, the host can reference this new state and update it on the
|
|
|
+ // ledger.
|
|
|
+ apply_state(&serialize(&state))?;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
Ok(())
|