Skip to content

Commit

Permalink
Accept ERC-20 tokens instead of ETH (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
guidanoli committed Jun 28, 2024
1 parent c788a75 commit 55dad32
Show file tree
Hide file tree
Showing 18 changed files with 641 additions and 267 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鈥攁 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

0 comments on commit 55dad32

Please sign in to comment.