diff --git a/contract/main.go b/contract/main.go index 6d660e8..4f2dc36 100644 --- a/contract/main.go +++ b/contract/main.go @@ -64,6 +64,7 @@ func (c *BugLessContract) Advance( Description: inputPayload.Description, Started: currTime, Deadline: inputPayload.Deadline, + Token: inputPayload.Token, InputIndex: metadata.InputIndex, Sponsorships: nil, Exploit: nil, @@ -100,21 +101,24 @@ func (c *BugLessContract) 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("token mismatch: %v (obtained) != %v (expected)", deposit.Token, 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 @@ -123,11 +127,11 @@ func (c *BugLessContract) 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) } @@ -163,7 +167,7 @@ func (c *BugLessContract) 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) } @@ -216,7 +220,7 @@ func (c *BugLessContract) 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) @@ -224,7 +228,7 @@ func (c *BugLessContract) Advance( } // 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) } diff --git a/shared/bugless.go b/shared/bugless.go index 02f897c..f366445 100644 --- a/shared/bugless.go +++ b/shared/bugless.go @@ -28,6 +28,7 @@ type AppBounty struct { Description string Started int64 // (unix timestamp) Deadline int64 // (unix timestamp) + Token common.Address // ERC-20 InputIndex int Sponsorships []*Sponsorship Exploit *Exploit @@ -78,11 +79,12 @@ 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"` // base64? + Name string `json:"name"` + ImgLink string `json:"imgLink"` + Description string `json:"description"` + Deadline int64 `json:"deadline"` // (unix timestamp) + CodeZipBinary string `json:"codeZipBinary"` // base64? + Token common.Address `json:"token"` // ERC-20 } func (b *CreateAppBounty) Validate() error { @@ -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"` diff --git a/tests/tests.lua b/tests/tests.lua index 6962297..4d62481 100644 --- a/tests/tests.lua +++ b/tests/tests.lua @@ -20,10 +20,14 @@ local SPONSOR1_WALLET = "0x0000000000000000000000000000000000000101" local HACKER1_WALLET = "0x0000000000000000000000000000000000000201" local HACKER2_WALLET = "0x0000000000000000000000000000000000000202" +local CTSI_ADDRESS = "0x491604c0fdf08347dd1fa4ee062a822a5dd06b5d" +local USDC_ADDRESS = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" +local WETH_ADDRESS = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + local config = { - ETHER_PORTAL_ADDRESS = "0xffdbe43d4c855bf7e0f105c400a50857f53ab044", + ERC20_PORTAL_ADDRESS = "0x9c21aeb2093c32ddbc53eef24b873bdcd1ada1db", DAPP_ADDRESS_RELAY_ADDRESS = "0xf5de34d6bbc0446e2a45719e718efebaae179dae", - DAPP_ADDRESS = "0x7122cd1221c20892234186facfe8615e6743ab02", + DAPP_ADDRESS = "0xab7528bb862fb57e8a2bcd567a2e929a0be56a5e", } local machine_config = ".sunodo/image" @@ -58,10 +62,11 @@ local function inspect_input(machine, opts) }, true)) end -local function advance_ether_deposit(machine, opts) +local function advance_erc20_deposit(machine, opts) return decode_response_jsons(machine:advance_state({ - metadata = { msg_sender = fromhex(config.ETHER_PORTAL_ADDRESS), timestamp = opts.timestamp }, - payload = cartesix_encoder.encode_ether_deposit({ + metadata = { msg_sender = fromhex(config.ERC20_PORTAL_ADDRESS), timestamp = opts.timestamp }, + payload = cartesix_encoder.encode_erc20_deposit({ + contract_address = fromhex(opts.token), sender_address = fromhex(opts.sender), amount = tobe256(opts.amount), extra_data = tojson{ kind = opts.kind, payload = opts.data }, @@ -114,6 +119,7 @@ describe("tests on Lua bounty", function() name = "Lua 5.4.3 Bounty", description = "Try to crash a sandboxed Lua 5.4.3 script", deadline = bounty_deadline, + token = CTSI_ADDRESS, codeZipBinary = tobase64(readfile(bounty_code)), }, }) @@ -132,6 +138,7 @@ describe("tests on Lua bounty", function() InputIndex = 1, Sponsorships = null, Started = bounty_started, + Token = CTSI_ADDRESS, Withdrawn = false, }, }, @@ -139,7 +146,8 @@ describe("tests on Lua bounty", function() end) it("should add sponsorship from developer itself", function() - local res = advance_ether_deposit(machine, { + local res = advance_erc20_deposit(machine, { + token = CTSI_ADDRESS, sender = DEVELOPER1_WALLET, amount = 1000, kind = "AddSponsorship", @@ -173,6 +181,7 @@ describe("tests on Lua bounty", function() }, }, Started = bounty_started, + Token = CTSI_ADDRESS, Withdrawn = false, }, }, @@ -180,7 +189,8 @@ describe("tests on Lua bounty", function() end) it("should add sponsorship from an external sponsor", function() - local res = advance_ether_deposit(machine, { + local res = advance_erc20_deposit(machine, { + token = CTSI_ADDRESS, sender = SPONSOR1_WALLET, amount = 2000, kind = "AddSponsorship", @@ -222,6 +232,7 @@ describe("tests on Lua bounty", function() }, }, Started = bounty_started, + Token = CTSI_ADDRESS, Withdrawn = false, }, }, @@ -243,6 +254,21 @@ describe("tests on Lua bounty", function() expect.equal(res.status, "rejected") end) + it("should reject sponsorship with the wrong token", function() + local res = advance_erc20_deposit(machine, { + token = USDC_ADDRESS, + sender = DEVELOPER1_WALLET, + amount = 1000, + kind = "AddSponsorship", + timestamp = timestamp, + data = { + name = "Developer1", + bountyIndex = bounty_index, + }, + }) + expect.equal(res.status, "rejected") + end) + it("should reject sponsor withdraw before deadline", function() local res = advance_input(machine, { sender = DEVELOPER1_WALLET, @@ -324,21 +350,22 @@ describe("tests on Lua bounty", function() }, }, Started = bounty_started, + Token = CTSI_ADDRESS, Withdrawn = true, }, }, }) expect.equal(res.vouchers, { { - address = fromhex(config.DAPP_ADDRESS), - payload = cartesix_encoder.encode_ether_transfer_voucher({ + address = fromhex(CTSI_ADDRESS), + payload = cartesix_encoder.encode_erc20_transfer_voucher({ destination_address = DEVELOPER1_WALLET, amount = tobe256(1000), }), }, { - address = fromhex(config.DAPP_ADDRESS), - payload = cartesix_encoder.encode_ether_transfer_voucher({ + address = fromhex(CTSI_ADDRESS), + payload = cartesix_encoder.encode_erc20_transfer_voucher({ destination_address = SPONSOR1_WALLET, amount = tobe256(2000), }), @@ -374,7 +401,8 @@ describe("tests on Lua bounty", function() end) it("should reject sponsorship after deadline", function() - local res = advance_ether_deposit(machine, { + local res = advance_erc20_deposit(machine, { + token = CTSI_ADDRESS, sender = SPONSOR1_WALLET, amount = 1000, kind = "AddSponsorship", @@ -464,6 +492,7 @@ describe("tests on SQLite bounty", function() name = "SQLite3 3.32.2 Bounty", description = "Try to crash SQLite 3.32.2 with a SQL query", deadline = bounty_deadline, + token = WETH_ADDRESS, codeZipBinary = tobase64(readfile(sqlite33202_bounty_code)), }, }) @@ -483,6 +512,7 @@ describe("tests on SQLite bounty", function() InputIndex = 5, Sponsorships = null, Started = bounty_started, + Token = WETH_ADDRESS, Withdrawn = false, }, }, @@ -490,7 +520,8 @@ describe("tests on SQLite bounty", function() end) it("should add sponsorship from an external sponsor", function() - local res = advance_ether_deposit(machine, { + local res = advance_erc20_deposit(machine, { + token = WETH_ADDRESS, sender = SPONSOR1_WALLET, amount = 4000, kind = "AddSponsorship", @@ -525,6 +556,7 @@ describe("tests on SQLite bounty", function() }, }, Started = bounty_started, + Token = WETH_ADDRESS, Withdrawn = false, }, }, @@ -532,7 +564,8 @@ describe("tests on SQLite bounty", function() end) it("should raise an sponsorship", function() - local res = advance_ether_deposit(machine, { + local res = advance_erc20_deposit(machine, { + token = WETH_ADDRESS, sender = SPONSOR1_WALLET, amount = 5000, kind = "AddSponsorship", @@ -567,6 +600,7 @@ describe("tests on SQLite bounty", function() }, }, Started = bounty_started, + Token = WETH_ADDRESS, Withdrawn = false, }, }, @@ -618,6 +652,7 @@ describe("tests on SQLite bounty", function() }, }, Started = bounty_started, + Token = WETH_ADDRESS, Withdrawn = true, }, }, @@ -625,8 +660,8 @@ describe("tests on SQLite bounty", function() second_bounty_final_state = res.state.Bounties[bounty_index + 1] expect.equal(res.vouchers, { { - address = fromhex(config.DAPP_ADDRESS), - payload = cartesix_encoder.encode_ether_transfer_voucher({ + address = fromhex(WETH_ADDRESS), + payload = cartesix_encoder.encode_erc20_transfer_voucher({ destination_address = HACKER1_WALLET, amount = tobe256(9000), }), @@ -661,7 +696,8 @@ describe("tests on SQLite bounty", function() end) it("should reject sponsorship after a previous exploit succeeded", function() - local res = advance_ether_deposit(machine, { + local res = advance_erc20_deposit(machine, { + token = WETH_ADDRESS, sender = SPONSOR1_WALLET, amount = 1000, kind = "AddSponsorship", @@ -691,6 +727,7 @@ describe("tests on BusyBox bounty", function() name = "BusyBox 1.36.1 Bounty", description = "Try to crash BusyBox 1.36.1", deadline = bounty_deadline, + token = USDC_ADDRESS, codeZipBinary = tobase64(readfile(sqlite33202_bounty_code)), }, }) @@ -711,6 +748,7 @@ describe("tests on BusyBox bounty", function() InputIndex = 9, Sponsorships = null, Started = bounty_started, + Token = USDC_ADDRESS, Withdrawn = false, }, }, @@ -754,14 +792,15 @@ describe("tests on BusyBox bounty", function() InputIndex = 9, Sponsorships = null, Started = bounty_started, + Token = USDC_ADDRESS, Withdrawn = true, }, }, }) expect.equal(res.vouchers, { { - address = fromhex(config.DAPP_ADDRESS), - payload = cartesix_encoder.encode_ether_transfer_voucher({ + address = fromhex(USDC_ADDRESS), + payload = cartesix_encoder.encode_erc20_transfer_voucher({ destination_address = HACKER1_WALLET, amount = tobe256(0), }),