Skip to content

Commit

Permalink
Merge pull request #12 from pooltogether/audit-fix-114-safemath
Browse files Browse the repository at this point in the history
use safemath add()
  • Loading branch information
PierrickGT committed Jul 8, 2021
2 parents d28c329 + 5326431 commit 9fecad5
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .solcover.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
skipFiles: ["test/RNGServiceMock.sol"],
skipFiles: ["test/"],
};
1 change: 0 additions & 1 deletion contracts/ISushiBar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ interface ISushiBar {
function totalSupply() external view returns (uint256);

function balanceOf(address account) external view returns (uint256);

}
70 changes: 49 additions & 21 deletions contracts/SushiYieldSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity 0.6.12;

import { IYieldSource } from "@pooltogether/yield-source-interface/contracts/IYieldSource.sol";
import "@pooltogether/yield-source-interface/contracts/IYieldSource.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

import "./ISushiBar.sol";
Expand All @@ -11,84 +11,112 @@ import "./ISushi.sol";
/// @title A pooltogether yield source for sushi token
/// @author Steffel Fenix
contract SushiYieldSource is IYieldSource {

using SafeMath for uint256;

ISushiBar public immutable sushiBar;
ISushi public immutable sushiAddr;

mapping(address => uint256) public balances;

/// @notice Emitted when asset tokens are redeemed from the yield source
event RedeemedToken(
address indexed from,
uint256 shares,
uint256 amount
);

/// @notice Emitted when asset tokens are supplied to the yield source
event SuppliedTokenTo(
address indexed from,
uint256 shares,
uint256 amount,
address indexed to
);

constructor(ISushiBar _sushiBar, ISushi _sushiAddr) public {
require(
address(_sushiBar) != address(0),
"SushiYieldSource/sushiBar-not-zero-address"
);
require(
address(_sushiAddr) != address(0),
"SushiYieldSource/sushiAddr-not-zero-address"
);

sushiBar = _sushiBar;
sushiAddr = _sushiAddr;
}

/// @notice Returns the ERC20 asset token used for deposits.
/// @return The ERC20 asset token
function depositToken() public view override returns (address) {
function depositToken() external view override returns (address) {
return address(sushiAddr);
}

/// @notice Returns the total balance (in asset tokens). This includes the deposits and interest.
/// @return The underlying balance of asset tokens
function balanceOfToken(address addr) public override returns (uint256) {
function balanceOfToken(address addr) external override returns (uint256) {
if (balances[addr] == 0) return 0;

uint256 totalShares = sushiBar.totalSupply();
uint256 barSushiBalance = sushiAddr.balanceOf(address(sushiBar));

return balances[addr].mul(barSushiBalance).div(totalShares);
return balances[addr].mul(barSushiBalance).div(totalShares);
}

/// @notice Allows assets to be supplied on other user's behalf using the `to` param.
/// @param amount The amount of `token()` to be supplied
/// @param to The user whose balance will receive the tokens
function supplyTokenTo(uint256 amount, address to) public override {
function supplyTokenTo(uint256 amount, address to) external override {
sushiAddr.transferFrom(msg.sender, address(this), amount);
sushiAddr.approve(address(sushiBar), amount);

ISushiBar bar = sushiBar;

sushi.transferFrom(msg.sender, address(this), amount);
sushi.approve(address(bar), amount);

uint256 beforeBalance = bar.balanceOf(address(this));

bar.enter(amount);

uint256 afterBalance = bar.balanceOf(address(this));
uint256 balanceDiff = afterBalance.sub(beforeBalance);

balances[to] = balances[to].add(balanceDiff);
emit SuppliedTokenTo(msg.sender, balanceDiff, amount, to);
}

/// @notice Redeems tokens from the yield source to the msg.sender, it burns yield bearing tokens and returns token to the sender.
/// @param amount The amount of `token()` to withdraw. Denominated in `token()` as above.
/// @dev The maxiumum that can be called for token() is calculated by balanceOfToken() above.
/// @return The actual amount of tokens that were redeemed. This may be different from the amount passed due to the fractional math involved.
function redeemToken(uint256 amount) public override returns (uint256) {
/// @return The actual amount of tokens that were redeemed. This may be different from the amount passed due to the fractional math involved.
function redeemToken(uint256 amount) external override returns (uint256) {
ISushiBar bar = sushiBar;
ISushi sushi = sushiAddr;

uint256 totalShares = bar.totalSupply();
if(totalShares == 0) return 0;
if (totalShares == 0) return 0;

uint256 barSushiBalance = sushi.balanceOf(address(bar));
if(barSushiBalance == 0) return 0;
if (barSushiBalance == 0) return 0;

uint256 sushiBeforeBalance = sushi.balanceOf(address(this));

uint256 requiredShares = ((amount.mul(totalShares) + totalShares)).div(barSushiBalance);
if(requiredShares == 0) return 0;
uint256 requiredShares = ((amount.mul(totalShares).add(totalShares))).div(barSushiBalance);
if (requiredShares == 0) return 0;

uint256 requiredSharesBalance = requiredShares.sub(1);
bar.leave(requiredSharesBalance);

uint256 sushiAfterBalance = sushi.balanceOf(address(this));

uint256 sushiBalanceDiff = sushiAfterBalance.sub(sushiBeforeBalance);

balances[msg.sender] = balances[msg.sender].sub(requiredSharesBalance);
sushi.transfer(msg.sender, sushiBalanceDiff);

emit RedeemedToken(msg.sender, requiredSharesBalance, amount);

return (sushiBalanceDiff);
}

}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"hardhat-typechain": "^0.3.5",
"prettier": "^2.2.1",
"prettier-plugin-solidity": "^1.0.0-beta.6",
"solidity-coverage": "^0.7.16",
"solidity-coverage": "0.7.16",
"ts-generator": "^0.1.1",
"ts-node": "^9.1.1",
"typechain": "^4.0.3",
Expand All @@ -39,10 +39,10 @@
"etherscan-verify": "hardhat etherscan-verify --network",
"test:integration": "FORK_MAINNET=true yarn hardhat test --network hardhat",
"test": "yarn hardhat test --network hardhat test/unit_test.js",
"lint": "yarn solhint 'contracts/SushiYieldSource.sol' && yarn prettier -c './**/*.js'",
"lint": "yarn solhint 'contracts/SushiYieldSource.sol'",
"format": "yarn prettier --write contracts/*.sol && yarn prettier --write test/*.js",
"hint": "solhint \"contracts/SushiYieldSource.sol\"",
"coverage": "FORK_MAINNET=true yarn hardhat coverage --testfiles \"test/*.js\"",
"coverage": "OPTIMIZER_DISABLED=true hardhat coverage --testfiles \"test/unit_test.js\"",
"coverage:file": "yarn hardhat coverage --testfiles",
"start-fork": "FORK_MAINNET=true hardhat node --no-reset",
"fork-run": "hardhat run --network localhost"
Expand Down
14 changes: 7 additions & 7 deletions scripts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,35 +34,35 @@ async function getYieldSourcePrizePoolProxy(tx) {
}

async function run() {
console.log("running fork script")
console.log("running fork script");

await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [SUSHI_HOLDER],
});

const SUSHI_TOKEN_ADDRESS = "0x6B3595068778DD592e39A122f4f5a5cF09C90fE2"
const XSUSHI_ADDRESS = "0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272"
const SUSHI_TOKEN_ADDRESS = "0x6B3595068778DD592e39A122f4f5a5cF09C90fE2";
const XSUSHI_ADDRESS = "0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272";

const sushiHolder = await ethers.provider.getUncheckedSigner(SUSHI_HOLDER);
const sushi = await ethers.getContractAt(
"IERC20Upgradeable",
SUSHI_TOKEN_ADDRESS,
sushiHolder
);
console.log("getting builder")
console.log("getting builder");
const builder = await ethers.getContractAt(
"PoolWithMultipleWinnersBuilder",
"0x39E2F33ff4Ad3491106B3BB15dc66EbE24e4E9C7"
);
console.log("deploying")
console.log("deploying");
SushiYieldSourceFactory = await ethers.getContractFactory("SushiYieldSource");
sushiYieldSource = await SushiYieldSourceFactory.deploy(
XSUSHI_ADDRESS,
SUSHI_TOKEN_ADDRESS
SUSHI_TOKEN_ADDRESS
);

console.log("deployed SushiYieldSource at ", sushiYieldSource.address)
console.log("deployed SushiYieldSource at ", sushiYieldSource.address);

const block = await ethers.provider.getBlock();

Expand Down
2 changes: 1 addition & 1 deletion test/integration_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe("SushiYieldSource integration", function () {
{ gasLimit: 9500000 }
);

const exchangeWalletAddress = "0xD551234Ae421e3BCBA99A0Da6d736074f22192FF";
const exchangeWalletAddress = "0xF977814e90dA44bFA03b6295A0616a897441aceC";
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [exchangeWalletAddress],
Expand Down
56 changes: 48 additions & 8 deletions test/unit_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ describe("SushiYieldSource", function () {
let yieldSource;
let amount;

let SushiYieldSourceContract;

let isDeployTest = false;

const deploySushiYieldSource = async (sushiBarAddress, sushiAddress) => {
yieldSource = await SushiYieldSourceContract.deploy(
sushiBarAddress,
sushiAddress,
overrides
);
};

beforeEach(async function () {
[wallet, wallet2] = await ethers.getSigners();
const ERC20MintableContract = await hre.ethers.getContractFactory(
Expand All @@ -33,21 +45,49 @@ describe("SushiYieldSource", function () {
);
sushiBar = await SushiBarContract.deploy(sushi.address);

const SushiYieldSourceContract = await ethers.getContractFactory(
SushiYieldSourceContract = await ethers.getContractFactory(
"SushiYieldSource"
);
yieldSource = await SushiYieldSourceContract.deploy(
sushiBar.address,
sushi.address,
overrides
);

if (!isDeployTest) {
deploySushiYieldSource(sushiBar.address, sushi.address);
}

amount = toWei("100");
await sushi.mint(wallet.address, amount);
await sushi.mint(wallet2.address, amount.mul(99));
await sushi.connect(wallet2).approve(sushiBar.address, amount.mul(99));
await sushiBar.connect(wallet2).enter(amount.mul(99));
});

describe("constructor()", () => {
let randomWalletAddress;

before(() => {
isDeployTest = true;
});

beforeEach(() => {
randomWalletAddress = ethers.Wallet.createRandom().address;
});

after(() => {
isDeployTest = false;
});

it("should fail if sushiBar address is address 0", async () => {
await expect(
deploySushiYieldSource(ethers.constants.AddressZero, sushi.address)
).to.be.revertedWith("SushiYieldSource/sushiBar-not-zero-address");
});

it("should fail if sushi address is address 0", async () => {
await expect(
deploySushiYieldSource(sushiBar.address, ethers.constants.AddressZero)
).to.be.revertedWith("SushiYieldSource/sushiAddr-not-zero-address");
});
});

it("get token address", async function () {
let address = await yieldSource.depositToken();
expect(address == sushi);
Expand All @@ -67,7 +107,7 @@ describe("SushiYieldSource", function () {

it("supplyTokenTo", async function () {
await sushi.connect(wallet).approve(yieldSource.address, amount);
await yieldSource.supplyTokenTo(amount, wallet.address);
expect(await yieldSource.supplyTokenTo(amount, wallet.address)).to.emit(yieldSource, "SuppliedTokenTo");
expect(await sushi.balanceOf(sushiBar.address)).to.eq(amount.mul(100));
expect(await yieldSource.callStatic.balanceOfToken(wallet.address)).to.eq(
amount
Expand All @@ -79,7 +119,7 @@ describe("SushiYieldSource", function () {
await yieldSource.supplyTokenTo(amount, wallet.address);

expect(await sushi.balanceOf(wallet.address)).to.eq(0);
await yieldSource.redeemToken(amount);
expect(await yieldSource.redeemToken(amount)).to.emit(yieldSource, "RedeemedToken");
expect(await sushi.balanceOf(wallet.address)).to.eq(amount);
});

Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8512,7 +8512,7 @@ solidity-comments-extractor@^0.0.4:
resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.4.tgz#ce420aef23641ffd0131c7d80ba85b6e1e42147e"
integrity sha512-58glBODwXIKMaQ7rfcJOrWtFQMMOK28tJ0/LcB5Xhu7WtAxk4UX2fpgKPuaL41XjMp/y0gAa1MTLqk018wuSzA==

solidity-coverage@^0.7.16:
solidity-coverage@0.7.16:
version "0.7.16"
resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.7.16.tgz#c8c8c46baa361e2817bbf275116ddd2ec90a55fb"
integrity sha512-ttBOStywE6ZOTJmmABSg4b8pwwZfYKG8zxu40Nz+sRF5bQX7JULXWj/XbX0KXps3Fsp8CJXg8P29rH3W54ipxw==
Expand Down

0 comments on commit 9fecad5

Please sign in to comment.