Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ppbl-faucet example #136

Open
aleeusgr opened this issue May 24, 2024 · 0 comments
Open

add ppbl-faucet example #136

aleeusgr opened this issue May 24, 2024 · 0 comments

Comments

@aleeusgr
Copy link
Owner

aleeusgr commented May 24, 2024

Your task is to implement a dApp component: Faucet.

A faucet smart contract is a type of smart contract designed to distribute a specific cryptocurrency or token in small amounts, typically for free, to users. It serves as a promotional tool or educational resource for a blockchain platform or cryptocurrency project. Faucet smart contracts are commonly used to distribute tokens to users before the official launch of a new project or to help bootstrap the adoption of an existing blockchain network.

How do you do that using Helios?
First, you need to describe what your component should do. This repo uses Helios Emulator to imagine how the state of the ledger changes as users interact with it. To imagine what our app should do we must imagine what is the state of the world before anything happens, and after the user interacts with the faucet.
In the comment, answer in your words: what happens when user talks to a Faucet. Read the description above or ask questions if you don't understand.

Now your goal is to formulate this question using computer code, approximately like so:

const oracle = await alice.utxos[2].value.dump().lovelace;
expect(oracle).toBe('9546007'); 

In this code we look at the wallet named Alice and see that the third UTXO contains certain amount of ADA. Why? Because it is the outcome of using the particular smart contract. Now your goal is to imagine what will be the outcome of using the smart contract you have do build, and then build its components until either it matches your expectation or you realize your expectation was not correct and you adjust accordingly.

Now lets look into steps to create the Faucet

1. Create the world model.

  • see the tests/template, tests/vesting-lock, tests/vesting-cancel;

First, we need to populate the program's world with the data it needs to know. This is called test context and is done in the beforeEach test in code block between lines 25 and 52; It is a good exercise to comment this block yourself to understand the logic of the program.

Notice, Script Context and Test Context are two different things. The test context includes all the information for the current test, including the particular instance of the ScriptContext in this test. ScriptContext, on another hand, is a strictly defined type which you can read about in the link above.

2. Instantiate the contract

Before we submit a transaction to withdraw the assets, we need to specify terms of the agreement. This is done by submitting a transaction that results in creation of a validatorAddress on-chain.

The acceptance criteria at this stage is like so:

\\ tests/vesting-lock.test
expect(Object.keys((await network.getUtxos(validatorAddress))[0].value.dump().assets)[0]).toBe('702cd6229f16532ca9735f65037092d099b0ff78a741c82db0847bbf');

Here we check that the UTXO at the validator address contains the token with the specified minting policy hash.

3. Address the instantiated contract.

  • This may be seen in tests/vesting-cancel.test

The next step is to implement a test that would show that your contract does what you say it does and fails if the conditions are broken. Let us look into how to build the following proposition.

The sender of the transaction is the holder of the PPBL ID token;

The example smart contract is located in src/vesting.hl file.

Below is the Vesting Redeemer.

enum Redeemer {
Cancel
Claim
}

It contains two sentences, but we only need to look into one (Cancel) to understand how a user is identified. The statement we will look at in natural language sounds like:

The sender of the transaction is the creator of the contract.

It is encoded like so:

func main(datum: Datum, redeemer: Redeemer, context: ScriptContext) -> Bool {
    tx: Tx = context.tx;
    ...
    redeemer.switch {
        Cancel => {
            ...
            // Check that the owner signed the transaction
            tx.is_signed_by(datum.creator).trace("VS2: ")
        },

Here the sentence Cancel is defined using Helios as a Domain-Specific Language.
First, the Redeemer needs access to information about who is the sender of the transaction. For this we use the ScriptContext, which wraps the Class Tx which contains the statement is_signed_by. Another useful method is inputs. This method returns the inputs of the current transaction, so from the inputs we just need to grab the first one (we should have only one anyway), and in it we must find Value Data Type, which will contain the token (if we attached it). This means we can identify the a token is being passed to the smart contract.

To specify the token, we use the second concept: the Datum, It is the piece that holds arbitrary data. ScriptContext has the things that are most useful in dApp logic, but Datum allows to store arbitrary values under some constraints. It has to be made up of certain data types that are described in the documentation. The Datum is created only when the validator is created. When the validator is addressed by the user, the user needs not be aware of the contents of the Datum.

With this context our proposition:

The sender of the transaction is the holder of the PPBL ID token;

becomes:

the transaction has a token attached to the inputs and it has the same identifier as the value stored under name chooseName in the Datum.

Represented as computer code it will look something like

tx_input.value.contains_policy(datum.policy).trace("VS: ")
  • define any suitable output for a transaction and verify it using a test.
  • implement token-gating: add a happy test (right data comes in and the tx succeeds) and a failing test ("forget: to add the token and see the tx fail)

4. Iterate and improve.

The acceptance criteria:

  • have a ppbl-faucet.hl file that contains the Faucet validator.

To demonstrate it works you need:

  • a create-faucet function.
  • a get-tokens function.
  • tests to verify correctness of your code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant