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

Accept ERC-20 tokens instead of ETH #87

Merged
merged 1 commit into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
80 changes: 53 additions & 27 deletions frontend/src/app/bounty/[bountyId]/exploit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import {
import { isNotEmpty, useForm } from "@mantine/form";

import { useWaitForTransaction } from "wagmi";

import { TestExploit } from "../../../../model/inputs";
import { usePrepareSendExploit } from "../../../../hooks/bug-buster";
import { useInputBoxAddInput } from "../../../../hooks/contracts";

import { BountyParams, ConcreteBountyParams } from "../utils.tsx";
import { useBounty } from "../../../../model/reader";
import { FileDrop } from "../../../../components/filedrop";
import { transactionStatus } from "../../../../utils/transactionStatus";

interface FileDropTextParams {
filename?: string;
Expand Down Expand Up @@ -53,21 +55,32 @@ interface SendExploitFormValues {
const SendExploitForm: FC<ConcreteBountyParams> = ({ bountyIndex, bounty }) => {
const [filename, setFilename] = useState<string>();

const form = useForm<SendExploitFormValues>({
initialValues: {
name: "",
exploit: "",
const initialValues: SendExploitFormValues = {
name: "",
exploit: "",
};

const form = useForm({
initialValues,
transformValues: (values) => {
return {
...values,
exploit: btoa(values.exploit),
};
},
validateInputOnBlur: true,
validateInputOnChange: true,
validate: {
name: isNotEmpty("An exploiter name is required"),
},
});

const { name, imgLink, exploit } = form.values;
const { name, imgLink, exploit } = form.getTransformedValues();

const [exploitOutput, setExploitOutput] = useState("");
const [exploitLoading, setExploitLoading] = useState(false);

async function testExploitAsync(bountyIndex: number, exploit: string) {
async function testExploitAsyncAux(bountyIndex: number, exploit: string) {
const inspectRequest: TestExploit = { bountyIndex, exploit };
setExploitOutput("Testing...");

Expand Down Expand Up @@ -96,24 +109,40 @@ const SendExploitForm: FC<ConcreteBountyParams> = ({ bountyIndex, bounty }) => {
}
}

async function testExploitAsync(bountyIndex: number, exploit: string) {
setExploitLoading(true);
try {
await testExploitAsyncAux(bountyIndex, exploit);
} catch (e) {
if (e instanceof Error) {
setExploitOutput(e.message);
}
}
setExploitLoading(false);
}

const testExploit = () => {
testExploitAsync(bountyIndex, exploit);
};

const config = usePrepareSendExploit({
const addInputPrepare = usePrepareSendExploit({
name,
imgLink,
bountyIndex,
exploit,
});

const { data, write } = useInputBoxAddInput(config);
const { isLoading, isSuccess } = useWaitForTransaction({
hash: data?.hash,
const addInputWrite = useInputBoxAddInput(addInputPrepare.config);

const addInputWait = useWaitForTransaction({
hash: addInputWrite.data?.hash,
});

const { disabled: addInputDisabled, loading: addInputLoading } =
transactionStatus(addInputPrepare, addInputWrite, addInputWait);

return (
<form onSubmit={form.onSubmit(() => write && write())}>
<form>
<Stack w={800}>
<Title size="h1">Submit exploit</Title>
<Text size="lg" fw={700} c="dimmed">
Expand Down Expand Up @@ -147,13 +176,7 @@ const SendExploitForm: FC<ConcreteBountyParams> = ({ bountyIndex, bounty }) => {
input: { fontFamily: "monospace" },
}}
placeholder="Exploit code"
error={form.getInputProps("exploit").error}
onChange={(e) =>
form.setFieldValue(
"exploit",
btoa(e.target.value),
)
}
{...form.getInputProps("exploit")}
/>
</Tabs.Panel>
<Tabs.Panel value="file">
Expand All @@ -170,7 +193,7 @@ const SendExploitForm: FC<ConcreteBountyParams> = ({ bountyIndex, bounty }) => {
.map((b) => String.fromCharCode(b))
.join("");

form.setFieldValue("exploit", btoa(str));
form.setFieldValue("exploit", str);
setFilename(fileWithPath.name);
});
}
Expand Down Expand Up @@ -207,19 +230,22 @@ const SendExploitForm: FC<ConcreteBountyParams> = ({ bountyIndex, bounty }) => {
</Paper>
)}
<Group justify="center" grow>
<Button size="lg" onClick={testExploit}>
<Button
size="lg"
loading={exploitLoading}
onClick={testExploit}
>
Test
</Button>
<Button
size="lg"
type="submit"
disabled={!write || isLoading || isSuccess}
disabled={addInputDisabled || !form.isValid()}
loading={addInputLoading}
onClick={() =>
addInputWrite.write && addInputWrite.write()
}
>
{isSuccess
? "Submitted!"
: isLoading
? "Submitting..."
: "Submit"}
Submit
</Button>
</Group>
</Stack>
Expand Down
Loading