Skip to content

Commit

Permalink
feat(grc20-launchpad): realms & pkg (#1263)
Browse files Browse the repository at this point in the history
* feat(erc20-launchpad): initialize token factory realm

* feat(erc20-launchpad): initialize gno mod

* feat(erc20-launchpad): add totalSupplyCap, allowMint and allowBurn to grc20factory

* feat: add 2.5% fee at creation of grc20 token

* feat: add airdrop contract

* feat(launchpad-grc20): add redeem function in airdrop contract

* fix(launchpad-grc20): resolve syntax errors raised by gnovm

* fix(launchpad-grc20): resolve syntax errors raised by gnovm

* feat(launchpad-grc20): add render in a separate file and set a welcome message on empty path

* chore(launchpad-grc20): gofumpt

* feat(launchpad-grc20): add mux router and define the render routes structure

* feat(launchpad-grc20): add rendering of token details

* feat(launchpad-grc20): add rendering of tokens balance for each user

* feat(launchpad-grc20): add redeem test of airdrop

* chore(launchpad-grc20): run gofumpt

* feat(launchpad-grc20): add render of airdrop details & claim check page

* feat(launchpad-grc20): add start & end date for airdrop

* fix(launchpad-grc20): save timestamp end & start into the airdrop struct instance

* feat(launchpad-grc20): add sale realm w/ creating and buying mechanism

* feat(launchpad-grc20): add sale render functions

* tests(launchpad-grc20): add airdrop tests

* tests(launchpad-grc20): add new token tests

* tests(launchpad-grc20): add mint token tests

* tests(launchpad-grc20): add burn token tests

* feat(launchpad-grc20): add buy function

* feat(launchpad-grc20): add refund mechanism for sale when not reaching the minimum goal

* fix(launchpad-grc20): fix sales syntax errors

* tests(launchpad-grc20): add sale creation test

* tests(launchpad-grc20): add sale buy & finalize test

* feat(launchpad-grc20): add ownable factory admin that can edit factory vault addr

* feat(launchpad-grc20): add optional merkle tree whitelist in sale

* tests(launchpad-grc20): add private sale test creation & buy mechanism

* chore(launchpad-grc20): run gofumpt & fix lint errors

* tests(launchpad-grc20): implement interface grc20.Token on our token type

* tests(launchpad-grc20): implement interface grc20.Token on our token type

* chore(launchpad-grc20): run gno mod tidy

* style(launchpad-grc20): add a page to display balance of a buyer for a specific sale

* feat(launchpad-grc20): add ClaimJSON function to pass the proofs through the function

* feat(launchpad-grc20): add BuyJSON function to pass the proofs through the function

* fix(launchpad-grc20): add json & hex imports on sale contract

* feat(launchpad-grc20): add render of 5 last tokens, airdrop & sale created & add hyperlinks

* feat(launchpad-grc20): add render of airdrops & sales linked to a token & add footer

* feat(launchpad-grc20): use seqid instead of basic uint64

* feat(launchpad-grc20): use gnoland/p/demo/merkle pkg & fix seqid display

* feat(launchpad-grc20): use a modified verision of merkle tree pkg

* fix(launchpad-grc20): initialize the sales & airdrop seqid at 0

* fix(p/demo/merkle): use constructor & position getter

* ci(launchpad-grc20): bump gno hash commit
  • Loading branch information
MikaelVallenet committed Sep 12, 2024
1 parent a9721bf commit 0d3b82f
Show file tree
Hide file tree
Showing 11 changed files with 2,320 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ start.gnodev-e2e:
.PHONY: clone-gno
clone-gno:
mkdir -p gnobuild
cd gnobuild && git clone https://github.com/gnolang/gno.git && cd gno && git checkout 9b114172063feaf2da4ae7ebb8263cada3ba699b
cd gnobuild && git clone https://github.com/gnolang/gno.git && cd gno && git checkout 8f800ece85a765113dfa4924da1c06f56865460c
cp -r ./gno/p ./gnobuild/gno/examples/gno.land/p/teritori

.PHONY: build-gno
Expand Down
8 changes: 8 additions & 0 deletions gno/p/jsonutil/jsonutil.gno
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ func MustUint64(value *json.Node) uint64 {
return uint64(MustInt(value)) // FIXME: full uint64 range support (currently limited to [-2^63, 2^63-1])
}

func Uint8Node(value uint8) *json.Node {
return json.StringNode("", strconv.FormatUint(uint64(value), 10))
}

func MustUint8(value *json.Node) uint8 {
return uint8(MustInt(value))
}

func AVLTreeNode(root *avl.Tree, transform func(elem interface{}) *json.Node) *json.Node {
if root == nil {
return EmptyObjectNode()
Expand Down
131 changes: 131 additions & 0 deletions gno/r/launchpad_grc20/airdrop_grc20.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package launchpad_grc20

import (
"encoding/hex"
"std"
"time"

"gno.land/p/demo/avl"
"gno.land/p/demo/json"
"gno.land/p/demo/merkle"
"gno.land/p/demo/seqid"
"gno.land/p/teritori/jsonutil"
)

type Airdrop struct {
token *Token
merkleRoot string
startTimestamp int64
endTimestamp int64
amountPerAddr uint64
alreadyClaimed *avl.Tree
}

var (
airdrops *avl.Tree // airdrop ID -> airdrop
nextAirdropID seqid.ID
)

func init() {
airdrops = avl.NewTree()
nextAirdropID = seqid.ID(0)
}

func NewAirdrop(tokenName, merkleRoot string, amountPerAddr uint64, startTimestamp, endTimestamp int64) uint64 {
token := mustGetToken(tokenName)
token.admin.AssertCallerIsOwner()

if !token.allowMint {
panic("token is not mintable")
}

now := time.Now().Unix()
if startTimestamp != 0 && startTimestamp < now {
panic("invalid start timestamp, must be in the future or be equal to 0 to start immediately")
}

if endTimestamp != 0 && endTimestamp < now {
panic("invalid end timestamp, must be in the future or be equal to 0 to never end")
}

if endTimestamp != 0 && startTimestamp >= endTimestamp {
panic("invalid timestamps, start must be before end")
}

airdrop := Airdrop{
token: token,
merkleRoot: merkleRoot,
startTimestamp: startTimestamp,
endTimestamp: endTimestamp,
amountPerAddr: amountPerAddr,
alreadyClaimed: avl.NewTree(),
}

airdropID := nextAirdropID.Next()
token.AirdropsIDs = append(token.AirdropsIDs, airdropID)

airdrops.Set(airdropID.String(), &airdrop)

return uint64(airdropID)
}

func ClaimJSON(airdropID uint64, proofsJSON string) {
nodes, err := json.Unmarshal([]byte(proofsJSON))
if err != nil {
panic("invalid json proofs")
}
vals := nodes.MustArray()
proofs := make([]merkle.Node, 0, len(vals))
for _, val := range vals {
obj := val.MustObject()
data, err := hex.DecodeString(obj["hash"].MustString())
if err != nil {
panic("invalid hex encoded hash")
}
node := merkle.NewNode(data, jsonutil.MustUint8(obj["pos"]))
proofs = append(proofs, node)
}
Claim(airdropID, proofs)
}

func Claim(airdropID uint64, proofs []merkle.Node) {
airdrop := mustGetAirdrop(airdropID)
caller := std.PrevRealm().Addr()

if !airdrop.isOnGoing() {
panic("airdrop is not ongoing, look at the airdrop period")
}

if airdrop.hasAlreadyClaimed(caller) {
panic("already claimed")
}

leaf := Leaf{[]byte(caller.String())}
if !merkle.Verify(airdrop.merkleRoot, leaf, proofs) {
panic("invalid proof")
}

airdrop.token.banker.Mint(caller, airdrop.amountPerAddr)

airdrop.alreadyClaimed.Set(caller.String(), true)
}

func mustGetAirdrop(airdropID uint64) *Airdrop {
id := seqid.ID(airdropID)
airdropRaw, exists := airdrops.Get(id.String())
if !exists {
panic("airdrop not found")
}

return airdropRaw.(*Airdrop)
}

func (a *Airdrop) hasAlreadyClaimed(caller std.Address) bool {
return a.alreadyClaimed.Has(caller.String())
}

func (a *Airdrop) isOnGoing() bool {
now := time.Now().Unix()
return (a.startTimestamp == 0 || a.startTimestamp <= now) &&
(a.endTimestamp == 0 || now < a.endTimestamp)
}
Loading

0 comments on commit 0d3b82f

Please sign in to comment.