Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

merkledrop/v1 package

Overview

Package merkledrop is a Merkle-gated airdrop that actually moves GNOT.

It is the successor to r/moul/x/daily/merkledrop/v0, which was generated by the daily pipeline and is deployed on mainnet. v0 works, but it is a demonstration rather than a drop, and its proof scheme is safe by accident. Everything below is what changed and why.

1. The leaf scheme is domain separated

v0 hashed leaves bare and combined nodes commutatively, the OpenZeppelin scheme: leaf = sha256(addr|amount), node = sha256(min||max). Without a leaf/inner tag an inner-node hash is also a valid leaf hash, so anyone who can present a 64-byte leaf preimage can prove membership of a leaf that was never committed.

v0 is not exploitable, but only by arithmetic: an inner preimage is exactly 64 bytes, and a v0 leaf preimage is at most 40 (bech32 address) + 1 + 20 (uint64 has at most 20 digits) = 61 bytes. 61 < 64, so no collision is reachable. Change the leaf encoding, widen the amount, use a different address form, and the forgery goes live with no visible diff at the call site. That is not a property to rely on.

v1 uses gno.land/p/moul/x/merkle/v0, which is the Tendermint scheme: leaves are tagged 0x00 and inner nodes 0x01, so the two preimage spaces cannot overlap at any length.

2. Proofs are bound to a position

v0's proof was a bare sibling list of any length, folded until it ran out. v1's proof carries its index and the total leaf count, and the verifier rebuilds the tree shape from them: a proof cannot be replayed at another index, and one of the wrong length is rejected rather than folded. The sibling count is capped at merkle.MaxDepth, so an untrusted caller cannot choose the length of the loop.

3. The root is settable, and the drop can close

v0's root is a `const`, so the drop can never be re-rooted, extended or ended. v1's owner sets the root, the leaf count and an optional closing height, and can sweep the remainder once it closes.

4. It moves real coins

v0 keeps a uint64 ledger and moves nothing; its README says so, but it sits on mainnet reading like an airdrop. v1 sends ugnot from the realm's own address through the banker, and refuses a claim it cannot pay rather than marking it claimed.

Who the claimer is

The claimer is PreviousRealm().Address(), the immediate caller. Called directly by a user that is the user; called through another realm it is that REALM. This is not a hole, because the leaf binds the address and the proof must match the claimer, so an intermediary can only claim an allocation granted to the intermediary itself. It does mean a wrapper realm cannot claim on a user's behalf, which is deliberate.

Built on gno.land/p/moul/x/merkle/v0.

Functions

Balance

func Balance() int64

Balance returns the ugnot the drop still holds.

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.Balance()"

Result

Claim

func Claim(cur realm, index int, amount int64, proof string)

Claim proves the caller is allocated amount at index and pays it out.

proof is the comma-separated hex sibling list from the tree generator, leaf first. It panics on a closed drop, a double claim, a bad proof, or a drop that cannot cover the amount; nothing is marked claimed in any of those cases.

Params

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/moul/x/daily/merkledrop/v1" -func "Claim" -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "gnoland-1" -remote "https://rpc.gno.land" ADDRESSgnokey query -remote "https://rpc.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/moul/x/daily/merkledrop/v1" -func "Claim" -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "gnoland-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.gno.land" call.tx
  

Claims

func Claims() int

Claims returns the number of allocations claimed.

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.Claims()"

Result

ClosesAt

func ClosesAt() int64

ClosesAt returns the last height a claim is accepted, or 0 for never.

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.ClosesAt()"

Result

DemoProof

func DemoProof(index int) string

DemoProof returns the proof for one seeded allocation, so the drop can be exercised end to end without an off-chain generator. It is empty once a real drop replaces the seeded one.

Param

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.DemoProof()"

Result

HasClaimed

func HasClaimed(index int) bool

HasClaimed reports whether the allocation at index was claimed.

Param

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.HasClaimed()"

Result

IsClosed

func IsClosed() bool

IsClosed reports whether the drop is past its closing height.

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.IsClosed()"

Result

Leaf

func Leaf(index int, addr address, amount int64) string

Leaf returns the exact preimage committed for one allocation. Reproduce it off chain to rebuild the tree; it is the whole interface between the drop and its generator.

Example
1leaf = "<pkgpath>|<index>|<address>|<amount>"

Params

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.Leaf(,,)"

Result

Paid

func Paid() int64

Paid returns the ugnot claimed so far.

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.Paid()"

Result

Render

func Render(path string) string

Render serves the drop overview at "" and one allocation at "proof/<index>".

Param

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.Render()"

Result

Root

func Root() string

Root returns the committed root, hex-encoded, or "" when no drop is set.

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.Root()"

Result

SetDrop

func SetDrop(cur realm, rootHex string, totalLeaves int, closesAtHeight int64)

SetDrop commits a new allocation root. Owner only.

totalLeaves is the number of allocations the root commits to; the verifier needs it to rebuild the tree shape, so a wrong value invalidates every proof rather than weakening any. closesAtHeight is the last height at which a claim is accepted, or 0 for a drop that never closes.

Setting a new root abandons the previous claim ledger: a drop is a commitment, and replacing it starts a new one.

Params

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/moul/x/daily/merkledrop/v1" -func "SetDrop" -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "gnoland-1" -remote "https://rpc.gno.land" ADDRESSgnokey query -remote "https://rpc.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/moul/x/daily/merkledrop/v1" -func "SetDrop" -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "gnoland-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.gno.land" call.tx
  

Sweep

func Sweep(cur realm)

Sweep returns whatever is left to the owner, once the drop has closed. Owner only. A drop with no closing height can never be swept, which is the point of setting one.

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/moul/x/daily/merkledrop/v1" -func "Sweep" -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "gnoland-1" -remote "https://rpc.gno.land" ADDRESSgnokey query -remote "https://rpc.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/moul/x/daily/merkledrop/v1" -func "Sweep" -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "gnoland-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.gno.land" call.tx
  

Total

func Total() int

Total returns the number of allocations the root commits to.

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.Total()"

Result

Verify

func Verify(index int, addr address, amount int64, proof string) bool

Verify checks an allocation against the committed root without claiming it, so a recipient can confirm a proof before spending gas on Claim.

Params

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/daily/merkledrop/v1.Verify(,,,)"

Result