# Part Two
This time, you don't start with any governance tokens!
Now, we have a Trader contract. This is a very simple version of automated market makers like Uniswap (opens new window) and Curve (opens new window), which allow users to exchange cryptocurrencies on a decentralized platform. While traditional market makers might set prices based on some real-world data, these protocols are much simpler. They allow liquidity providers to deposit funds into "pools," and prices are determined by the relative ratios of different tokens. Specifically, these protocols generally hold some kind of invariant—the simplest being a constant product of liquidity pool volumes—which allows prices to be determined by the market through arbitrage.
While Uniswap has hundreds of verified pools across many different chains and allows users to create many more, our Trader contract has just one:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import 'OpenZeppelin/openzeppelin-contracts@4.4.2/contracts/utils/math/SafeMath.sol';
import 'OpenZeppelin/openzeppelin-contracts@4.4.2/contracts/token/ERC20/IERC20.sol';
contract Trader {
using SafeMath for uint;
IERC20 public token0;
IERC20 public token1;
constructor(IERC20 _token0, IERC20 _token1) {
token0 = _token0;
token1 = _token1;
}
function swap(int amount0, int amount1) external {
uint productBefore = token0.balanceOf(address(this)).mul(token1.balanceOf(address(this)));
if (amount0 < 0) token0.transferFrom(msg.sender, address(this), uint(-amount0));
else token0.transfer(msg.sender, uint(amount0));
if (amount1 < 0) token1.transferFrom(msg.sender, address(this), uint(-amount1));
else token1.transfer(msg.sender, uint(amount1));
uint productAfter = token0.balanceOf(address(this)).mul(token1.balanceOf(address(this)));
require(productAfter >= productBefore, "no deal");
}
}
Just like Uniswap, this protocol works by maintaining a constant product
between its two token volumes. If it has 100 of token0
and 200 of token1
,
the protocol makes sure that after the next trade, the new product of the
volumues is greater than 100 x 200 = 20000
. This means that if the balance in
token0
goes down, the relative price of token1
increases: the opposite is
true when the balance increases.
Another decentralized finance instrument in play is the flash loans from the tutorial. Can you take inspiration from the Beanstalk governance exploit and take control of the governance contract?
Instance contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import 'OpenZeppelin/openzeppelin-contracts@4.4.2/contracts/proxy/ERC1967/ERC1967Proxy.sol';
import '../challenge1/Governance.sol';
import '../tutorial/LoanToken.sol';
import './Lender.sol';
import './Trader.sol';
contract PartTwo {
uint256 constant public TOTAL_SUPPLY = 1e18;
IERC20 public govToken;
IERC20 public loanToken;
Governance public governance;
Lender public lender;
Trader public trader;
address player;
constructor(address _player) {
player = _player;
governance = new Governance(address(this));
govToken = governance.token();
loanToken = new LoanToken(TOTAL_SUPPLY);
loanToken.transfer(address(0x1), 1e17);
govToken.transfer(address(0x1), 4e17);
lender = new Lender();
loanToken.transfer(address(lender), 8e17);
trader = new Trader(loanToken, govToken);
loanToken.transfer(address(trader), 1e17);
govToken.transfer(address(trader), 6e17);
}
function solved() view external returns (bool) {
return governance.owner() == player;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import 'OpenZeppelin/openzeppelin-contracts@4.4.2/contracts/token/ERC20/IERC20.sol';
interface ReceiverInterface {
function receiveLoan(address token_address) external returns (bool);
}
contract Lender {
function flashLoan(IERC20 token, address borrower, uint256 borrowAmount) external {
require(borrowAmount > 0, "Borrow amount must be greater than 0");
uint256 balanceBefore = token.balanceOf(address(this));
require(balanceBefore >= borrowAmount, "Not enough ETH in pool");
token.transfer(borrower, borrowAmount);
require(token.balanceOf(address(borrower)) == borrowAmount, "Borrower did not receive loan");
ReceiverInterface borrowerInterface = ReceiverInterface(borrower);
bool success = borrowerInterface.receiveLoan(address(token));
require(success, "Call to receiveLoan(token_address) failed");
require(token.balanceOf(address(this)) == balanceBefore, "Loan was not repaid!");
}
}
Governance.sol
and GovTokenLogic.sol
are unchanged from Part 1.