Skip to content

Commit

Permalink
Accept ERC-20 tokens instead of ETH
Browse files Browse the repository at this point in the history
  • Loading branch information
guidanoli committed May 28, 2024
1 parent bd0031d commit 4654b75
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 72 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ As a result, developers are able to unfairly underpay whitehats, or even refuse

To solve this issue, we have developed Bug Buster—a trustless bug bounty platform powered by [Cartesi Rollups](https://www.cartesi.io/).
Running inside a deterministic RISC-V machine that boots Linux, Bug Buster accepts applications written in any major programming language[^1].
Through a friendly web interface, anyone can submit applications, and sponsor them with Ether to incentivize hackers! All major wallets are supported[^2].
Through a friendly web interface, anyone can submit applications, and sponsor them with ERC-20 tokens to incentivize hackers! All major wallets are supported[^2].
Meanwhile, hackers can test their exploits right on the browser, without even having to sign Web3 transactions!
Once the hacker finds a valid exploit, they can finally send a transaction requesting the reward to be transferred to their account.
If, however, no one is able to submit a valid exploit until a certain deadline, the sponsors may request a refund.
Expand Down
10 changes: 5 additions & 5 deletions cli/cmd/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os/exec"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/rollmelette/rollmelette"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -52,15 +53,14 @@ func sendDo(inputKind shared.InputKind, payload any, send func(string, context.C
log.Printf("input added\n%s", output)
}

func sendEther(txValue *big.Int, inputKind shared.InputKind, payload any) {
func sendERC20(token common.Address, value *big.Int, inputKind shared.InputKind, payload any) {
sendDo(inputKind, payload, func(inputJsonStr string, ctx context.Context) ([]byte, error) {
cmd := exec.CommandContext(ctx,
"cast", "send",
"--unlocked", "--from", sendArgs.fromAddress,
"--value", txValue.String(),
addressBook.EtherPortal.String(), // TO
"depositEther(address,bytes)", // SIG
dappAddress, inputJsonStr, // ARGS
addressBook.ERC20Portal.String(), // TO
"depositERC20Tokens(address,address,uint256,bytes)", // SIG
token.String(), dappAddress, value.String(), inputJsonStr, // ARGS
)
return cmd.CombinedOutput()
})
Expand Down
18 changes: 11 additions & 7 deletions cli/cmd/sponsor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/spf13/cobra"
)

Expand All @@ -18,24 +19,23 @@ var (
sponsorBountyIndex int
sponsorName string
sponsorImgLink string
sponsorToken string
sponsorValue string
)

func sponsorRun(cmd *cobra.Command, args []string) {
etherValue, ok := new(big.Float).SetString(sponsorValue)
value, ok := new(big.Int).SetString(sponsorValue, 10)
if !ok {
log.Fatalf("failed to parse value")
return
}
tenToEighteen := new(big.Float).SetFloat64(1e18)
weiValue := new(big.Float).Mul(etherValue, tenToEighteen)
value := new(big.Int)
weiValue.Int(value)
token := common.HexToAddress(sponsorToken)
payload := &shared.AddSponsorship{
BountyIndex: sponsorBountyIndex,
Name: sponsorName,
ImgLink: sponsorImgLink,
}
sendEther(value, shared.AddSponsorshipInputKind, payload)
sendERC20(token, value, shared.AddSponsorshipInputKind, payload)
}

func init() {
Expand All @@ -53,6 +53,10 @@ func init() {
&sponsorImgLink, "image", "i", "", "Sponsor image")

sponsorCmd.Flags().StringVarP(
&sponsorValue, "value", "v", "", "Value to sponsor in Ether")
&sponsorToken, "token", "t", "", "Address of ERC-20 token")
sponsorCmd.MarkFlagRequired("token")

sponsorCmd.Flags().StringVarP(
&sponsorValue, "value", "v", "", "Amount of tokens to sponsor")
sponsorCmd.MarkFlagRequired("value")
}
28 changes: 16 additions & 12 deletions contract/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func (c *BugBusterContract) Advance(
ImgLink: inputPayload.ImgLink,
Description: inputPayload.Description,
Deadline: inputPayload.Deadline,
Token: inputPayload.Token,
Sponsorships: nil,
Exploit: nil,
Withdrawn: false,
Expand Down Expand Up @@ -108,21 +109,24 @@ func (c *BugBusterContract) Advance(
return fmt.Errorf("can't add sponsorship after deadline")
}

var etherDepositSender common.Address
var etherDepositValue *uint256.Int
var erc20DepositSender common.Address
var erc20DepositValue *uint256.Int

switch deposit := deposit.(type) {
case *rollmelette.EtherDeposit:
etherDepositSender = deposit.Sender
etherDepositValue, _ = uint256.FromBig(deposit.Value)
case *rollmelette.ERC20Deposit:
if deposit.Token != bounty.Token {
return fmt.Errorf("wrong token: %v", bounty.Token)
}
erc20DepositSender = deposit.Sender
erc20DepositValue, _ = uint256.FromBig(deposit.Amount)
default:
return fmt.Errorf("unsupported deposit: %T", deposit)
}

sponsorship := bounty.GetSponsorship(etherDepositSender)
sponsorship := bounty.GetSponsorship(erc20DepositSender)
if sponsorship != nil {
// Add to existing sponsorship
newValue := new(uint256.Int).Add(sponsorship.Value, etherDepositValue)
newValue := new(uint256.Int).Add(sponsorship.Value, erc20DepositValue)
sponsorship.Value = newValue
// Update profile
sponsorship.Sponsor.Name = inputPayload.Name
Expand All @@ -131,11 +135,11 @@ func (c *BugBusterContract) Advance(
// Create new sponsorship
sponsorship := &shared.Sponsorship{
Sponsor: shared.Profile{
Address: etherDepositSender,
Address: erc20DepositSender,
Name: inputPayload.Name,
ImgLink: inputPayload.ImgLink,
},
Value: etherDepositValue,
Value: erc20DepositValue,
}
bounty.Sponsorships = append(bounty.Sponsorships, sponsorship)
}
Expand Down Expand Up @@ -171,7 +175,7 @@ func (c *BugBusterContract) Advance(

// generate voucher for each sponsor
for _, sponsorship := range bounty.Sponsorships {
_, err := env.EtherWithdraw(sponsorship.Sponsor.Address, sponsorship.Value.ToBig())
_, err := env.ERC20Withdraw(bounty.Token, sponsorship.Sponsor.Address, sponsorship.Value.ToBig())
if err != nil {
return fmt.Errorf("failed to withdraw: %v", err)
}
Expand Down Expand Up @@ -224,15 +228,15 @@ func (c *BugBusterContract) Advance(
if sponsor == hacker {
continue
}
err := env.EtherTransfer(sponsor, hacker, sponsorship.Value.ToBig())
err := env.ERC20Transfer(bounty.Token, sponsor, hacker, sponsorship.Value.ToBig())
if err != nil {
// this should be impossible
return fmt.Errorf("failed to transfer asset: %v", err)
}
}

// generate voucher
_, err := env.EtherWithdraw(hacker, accBounty.ToBig())
_, err := env.ERC20Withdraw(bounty.Token, hacker, accBounty.ToBig())
if err != nil {
return fmt.Errorf("failed to generate voucher: %v", err)
}
Expand Down
30 changes: 21 additions & 9 deletions frontend/src/app/bounty/[bountyId]/sponsor/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
Title,
Text,
} from "@mantine/core";
import { FC, useState } from "react";
import { parseEther } from "viem";
import { FC, useState, useEffect } from "react";
import { Address, parseEther } from "viem";
import { AddSponsorship } from "../../../../model/inputs";
import { usePrepareAddSponsorship } from "../../../../hooks/bug-buster";
import { useEtherPortalDepositEther } from "../../../../hooks/contracts";
import { useErc20PortalDepositErc20Tokens } from "../../../../hooks/contracts";
import { useWaitForTransaction } from "wagmi";

import { BountyParams } from "../utils.tsx";
Expand All @@ -35,18 +35,32 @@ const AddSponsorshipPage: FC<BountyParams> = ({ params: { bountyId } }) => {
const [name, setName] = useState("");
const [imgLink, setImgLink] = useState("");
const [value, setValue] = useState(0);
const [token, setToken] = useState<Address>();

const bountyIndex = Number(bountyId);

const addSponsorship = {
const bountyResult = useBounty(bountyIndex);

useEffect(() => {
if (bountyResult.kind == "success") {
const bounty = bountyResult.response;
setToken(bounty.token);
}
}, [bountyResult]);

const addSponsorship: AddSponsorship = {
name,
imgLink,
bountyIndex,
} as AddSponsorship;
};

const config = usePrepareAddSponsorship(addSponsorship, toWei(value));
const config = usePrepareAddSponsorship(
addSponsorship,
token ?? "0x0000000000000000000000000000000000000000",
toWei(value)
);

const { data, write } = useEtherPortalDepositEther(config);
const { data, write } = useErc20PortalDepositErc20Tokens(config);
const { isLoading, isSuccess } = useWaitForTransaction({
hash: data?.hash,
});
Expand All @@ -55,8 +69,6 @@ const AddSponsorshipPage: FC<BountyParams> = ({ params: { bountyId } }) => {
return (e: any) => setter(e.target.value);
}

const bountyResult = useBounty(bountyIndex);

switch (bountyResult.kind) {
case "loading":
return <Center>Loading bounty info...</Center>;
Expand Down
20 changes: 19 additions & 1 deletion frontend/src/app/bounty/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "@mantine/core";
import { DateInput } from "@mantine/dates";
import { FileWithPath } from "@mantine/dropzone";
import { Address, isAddress } from "viem";

import { useInputBoxAddInput } from "../../../hooks/contracts";
import { useWaitForTransaction } from "wagmi";
Expand Down Expand Up @@ -53,6 +54,7 @@ const CreateBountyPage: FC = () => {
const [name, setName] = useState<string>();
const [description, setDescription] = useState<string>();
const [imgLink, setImgLink] = useState<string>();
const [token, setToken] = useState<Address>();
const [filename, setFilename] = useState<string>();
const [minDeadline, setMinDeadline] = useState<Date>();
const [deadline, setDeadline] = useState<Date>();
Expand Down Expand Up @@ -93,6 +95,7 @@ const CreateBountyPage: FC = () => {
deadline: (deadline ?? new Date()).getTime() / 1000,
codeZipBinary,
codeZipPath,
token: token ?? "0x0000000000000000000000000000000000000000",
};

const config = usePrepareCreateBounty(bounty);
Expand Down Expand Up @@ -130,6 +133,19 @@ const CreateBountyPage: FC = () => {
placeholder="https://"
onChange={(e) => setImgLink(e.target.value)}
/>
<TextInput
withAsterisk
size="lg"
label="Token address"
value={token}
placeholder="0x"
onChange={(e) => {
const newToken = e.target.value;
if (isAddress(newToken)) {
setToken(newToken);
}
}}
/>

<DateInput
withAsterisk
Expand Down Expand Up @@ -189,7 +205,9 @@ const CreateBountyPage: FC = () => {
!name ||
name.trim().length === 0 ||
!description ||
description.trim().length === 0
description.trim().length === 0 ||
!token ||
!isAddress(token)
}
>
{isLoading ? "Creating Bounty..." : "Create Bounty"}
Expand Down
24 changes: 16 additions & 8 deletions frontend/src/hooks/bug-buster.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { toHex, Hex } from "viem";
import { toHex, Hex, Address } from "viem";
import {
usePrepareEtherPortalDepositEther,
usePrepareErc20PortalDepositErc20Tokens,
usePrepareInputBoxAddInput,
} from "./contracts";
import {
Expand All @@ -25,26 +25,34 @@ function usePrepareBugBusterInput(input: Input) {
return config;
}

function usePrepareBugBusterETHDeposit(input: Input, valueInWei: bigint) {
const { config } = usePrepareEtherPortalDepositEther({
args: [getDAppAddress(), encodeInput(input)],
value: valueInWei,
function usePrepareBugBusterErc20Deposit(
input: Input,
token: Address,
value: bigint,
) {
const { config } = usePrepareErc20PortalDepositErc20Tokens({
args: [token, getDAppAddress(), value, encodeInput(input)],
enabled: true,
});

return config;
}

export function usePrepareCreateBounty(bounty: CreateAppBounty) {
return usePrepareBugBusterInput({ kind: "CreateAppBounty", payload: bounty });
return usePrepareBugBusterInput({
kind: "CreateAppBounty",
payload: bounty,
});
}

export function usePrepareAddSponsorship(
sponsorship: AddSponsorship,
token: Address,
value: bigint,
) {
return usePrepareBugBusterETHDeposit(
return usePrepareBugBusterErc20Deposit(
{ kind: "AddSponsorship", payload: sponsorship },
token,
value,
);
}
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/model/inputs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Hex } from "viem";
import { Address, Hex } from "viem";

import { CompletionStatus } from "./__generated__/graphql";

Expand All @@ -9,6 +9,7 @@ export interface CreateAppBounty {
deadline: number;
codeZipBinary?: string;
codeZipPath?: string;
token: Address;
}

export interface AddSponsorship {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/model/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface AppBounty {
imgLink?: string;
description: string;
deadline: number;
token: Address;
sponsorships: Sponsorship[] | null;
exploit: Exploit | null;
withdrawn: boolean;
Expand Down
20 changes: 11 additions & 9 deletions shared/bug-buster.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ func (s *BugBusterState) GetBounty(bountyIndex int) *AppBounty {

type AppBounty struct {
Name string `json:"name"`
ImgLink string `json:"imgLink"` // optional
ImgLink string `json:"imgLink"` // optional
Description string `json:"description"`
Deadline int64 `json:"deadline"` // (unix timestamp)
Deadline int64 `json:"deadline"` // (unix timestamp)
Token common.Address `json:"token"` // ERC-20
Sponsorships []*Sponsorship `json:"sponsorships"`
Exploit *Exploit `json:"exploit"`
Withdrawn bool `json:"withdrawn"`
Expand Down Expand Up @@ -77,12 +78,13 @@ type Input struct {
}

type CreateAppBounty struct {
Name string `json:"name"`
ImgLink string `json:"imgLink"`
Description string `json:"description"`
Deadline int64 `json:"deadline"` // (unix timestamp)
CodeZipBinary *string `json:"codeZipBinary,omitempty"` // base64?
CodeZipPath *string `json:"codeZipPath,omitempty"`
Name string `json:"name"`
ImgLink string `json:"imgLink"`
Description string `json:"description"`
Deadline int64 `json:"deadline"` // (unix timestamp)
CodeZipBinary *string `json:"codeZipBinary,omitempty"` // base64?
CodeZipPath *string `json:"codeZipPath,omitempty"`
Token common.Address `json:"token"` // ERC-20
}

func (b *CreateAppBounty) Validate() error {
Expand All @@ -101,7 +103,7 @@ func (b *CreateAppBounty) Validate() error {
return nil
}

// From portal (Ether)
// From portal (ERC-20)
type AddSponsorship struct {
BountyIndex int `json:"bountyIndex"`
Name string `json:"name"`
Expand Down
Loading

0 comments on commit 4654b75

Please sign in to comment.