Skip to content

Instantly share code, notes, and snippets.

@jswny
Last active November 20, 2020 04:42
Show Gist options
  • Save jswny/2d738c1bb6d859ce1dbc28617b8e28e5 to your computer and use it in GitHub Desktop.
Save jswny/2d738c1bb6d859ce1dbc28617b8e28e5 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.6.12+commit.27d51765.js&optimize=false&runs=200&gist=
Homework 5: Ethereum Tokens
0. Upload all of the .sol files in this archive into the remix IDE.
1. Implement a "TokenHolder" contract that derives from ITokenHolder. Implement the TokenManager contract. The TokenManager creates the token type, and mints and melts tokens on demand, at a defined exchange rate. Its basically the market maker and provides an essentially infinite "pool" of tokens to draw from. Therefre it is BOTH an ERC223Token and a TokenHolder.
Token sales occur in 2 steps. The TokenHolder must first make some tokens available for sale by calling "putUpForSale". Then the buyer can call the "sellToCaller" function (with the appropriate payment) and this function will transfer tokens from seller to buyer.
1a. To move tokens between 2 contracts without a sale (presumably owned by the same entity), implement the "withdraw function. Hint: This is a 1-liner.
1b. Implement "remit" which sells tokens back to the token manager. This is a little bit of a special case because anyone can tell the TokenMgr to initiate a buy (whereas the normal TokenHolder only buys if the owner tells it to). (Hint, use the TokenManager's buyFromCaller function). Implement TokenManager.buyFromCaller.
2. Implement all the other helper functions.
Don't forget to add the appropriate require statements to ensure that the money and tokens are appropriately transfered. Remember that two TokenHolders or a TokenHolder and a TokenManager are INDEPENDENT, COMPETITIVE entities. So they will steal tokens from eachother if possible!
3. What is the security flaw in the TokenHolder's sell API definition? Write a short function that profits from it. How can you fix it in the implementation?
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* This test is non-exhaustive, and there may be false-negatives: during the
* execution of a contract's constructor, its address will be reported as
* not containing a contract.
*
* > It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*/
function isContract(address account) internal view returns (bool) {
// This method relies in extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
/**
* @dev Converts an `address` into `address payable`. Note that this is
* simply a type cast: the actual underlying value is not changed.
*/
function toPayable(address account) internal pure returns (address payable) {
return address(uint160(account));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
import "./IERC223.sol";
import "./safeMath.sol";
import "./Address.sol";
/**
* @title Reference implementation of the ERC223 standard token.
*/
contract ERC223Token is IERC223 {
using SafeMath for uint;
/**
* @dev See `IERC223.totalSupply`.
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
mapping(address => uint) balances; // List of user balances.
/**
* @dev Transfer the specified amount of tokens to the specified address.
* Invokes the `tokenFallback` function if the recipient is a contract.
* The token transfer fails if the recipient is a contract
* but does not implement the `tokenFallback` function
* or the fallback function to receive funds.
*
* @param _to Receiver address.
* @param _value Amount of tokens that will be transferred.
* @param _data Transaction metadata.
*/
function transfer(address _to, uint _value, bytes memory _data) override public returns (bool success){
// Standard function transfer similar to ERC20 transfer with no _data .
// Added due to backwards compatibility reasons .
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
if(Address.isContract(_to)) {
IERC223Recipient receiver = IERC223Recipient(_to);
receiver.tokenFallback(msg.sender, _value, _data);
}
emit Transfer(msg.sender, _to, _value, _data);
return true;
}
/**
* @dev Transfer the specified amount of tokens to the specified address.
* This function works the same with the previous one
* but doesn't contain `_data` param.
* Added due to backwards compatibility reasons.
*
* @param _to Receiver address.
* @param _value Amount of tokens that will be transferred.
*/
function transfer(address _to, uint _value) override public returns (bool success){
bytes memory empty = hex"00000000";
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
if(Address.isContract(_to)) {
IERC223Recipient receiver = IERC223Recipient(_to);
receiver.tokenFallback(msg.sender, _value, empty);
}
emit Transfer(msg.sender, _to, _value, empty);
return true;
}
/**
* @dev Returns balance of the `_owner`.
*
* @param _owner The address whose balance will be returned.
* @return balance Balance of the `_owner`.
*/
function balanceOf(address _owner) override public view returns (uint balance) {
return balances[_owner];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
/**
* @dev Interface of the ERC777Token standard as defined in the EIP.
*
* This contract uses the
* [ERC1820 registry standard](https://eips.ethereum.org/EIPS/eip-1820) to let
* token holders and recipients react to token movements by using setting implementers
* for the associated interfaces in said registry. See `IERC1820Registry` and
* `ERC1820Implementer`.
*
* https://github.com/ethereum/eips/issues/223
*/
abstract contract IERC223 {
/**
* @dev Returns the total supply of the token.
*/
uint public _totalSupply;
/**
* @dev Returns the balance of the `who` address.
*/
function balanceOf(address who) virtual external view returns (uint);
/**
* @dev Transfers `value` tokens from `msg.sender` to `to` address
* and returns `true` on success.
*/
function transfer(address to, uint value) virtual external returns (bool success);
/**
* @dev Transfers `value` tokens from `msg.sender` to `to` address with `data` parameter
* and returns `true` on success.
*/
function transfer(address to, uint value, bytes memory data) virtual external returns (bool success);
/**
* @dev Event that is fired on successful transfer.
*/
event Transfer(address indexed from, address indexed to, uint value, bytes data);
}
/**
* @title Contract that will work with ERC223 tokens.
*/
abstract contract IERC223Recipient {
/**
* @dev Standard ERC223 function that will handle incoming token transfers.
*
* @param _from Token sender address.
* @param _value Amount of tokens.
* @param _data Transaction metadata.
*/
function tokenFallback(address _from, uint _value, bytes memory _data) virtual external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
/*
* Ownable
*
* Base contract with an owner.
* Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
*/
contract Ownable {
address public owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function.");
_;
}
function transferOwnership(address newOwner) external onlyOwner {
if (newOwner != address(0)) owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
import "./IERC223.sol";
import "./ERC223.sol";
import "./ownable.sol";
abstract contract ITokenHolder is IERC223Recipient, Ownable
{
IERC223 public currency;
uint256 public pricePer; // In wei
uint256 public amtForSale;
// Return the current balance of ethereum held by this contract
function ethBalance() view external returns (uint)
{
return address(this).balance;
}
// Return the quantity of tokens held by this contract
function tokenBalance() virtual external view returns(uint);
// indicate that this contract has tokens for sale at some price, so buyFromMe will be successful
function putUpForSale(uint amt, uint price) virtual public
{
assert(false);
}
// This function is called by the buyer to pay in ETH and receive tokens. Note that this contract should ONLY sell the amount of tokens at the price specified by putUpForSale!
function sellToCaller(address to, uint qty) virtual external payable
{
assert(false);
}
// buy tokens from another holder. This is OPTIONALLY payable. The caller can provide the purchase ETH, or expect that the contract already holds it.
function buy(uint amt, uint maxPricePer, TokenHolder seller) virtual public payable onlyOwner
{
assert(false);
}
// Owner can send tokens
function withdraw(address _to, uint amount) virtual public onlyOwner
{
assert(false);
}
// Sell my tokens back to the token manager
function remit(uint amt, uint _pricePer, TokenManager mgr) virtual public onlyOwner payable
{
assert(false);
}
// Validate that this contract can handle tokens of this type
// You need to define this function in your derived classes, but it is already specified in IERC223Recipient
//function tokenFallback(address _from, uint /*_value*/, bytes memory /*_data*/) override external
}
contract TokenHolder is ITokenHolder
{
constructor(IERC223 _cur) public
{
currency = _cur;
}
// Implement all ITokenHolder functions and tokenFallback
function tokenBalance() override external view returns (uint) {
return currency.balanceOf(address(this));
}
function putUpForSale(uint amt, uint price) override public {
assert(false);
}
// function sellToCaller(address to, uint qty) override external payable
// {
// assert(false);
// }
function buy(uint amt, uint maxPricePer, TokenHolder seller) override public payable onlyOwner
{
assert(false);
}
// Owner can send tokens
function withdraw(address _to, uint amount) override public onlyOwner
{
assert(false);
}
// Sell my tokens back to the token manager
function remit(uint amt, uint _pricePer, TokenManager mgr) override public onlyOwner payable
{
assert(false);
}
function tokenFallback(address _from, uint _value, bytes memory _data) override external {
assert(false);
}
}
contract TokenManager is ERC223Token, TokenHolder
{
// Implement all functions
// Pass the price per token, and the fee per token to set up the manager's buy/sell activity
constructor(uint price, uint fee) TokenHolder(this) public payable
{
}
// Returns the price per token to callers
function price(uint amt) public view returns(uint)
{
}
// Returns the fee per token to callers
function fee(uint amt) public view returns(uint)
{
}
// Caller buys tokens from this contract
function sellToCaller(address to, uint amount) payable override public
{
}
// Caller sells tokens to this contract
function buyFromCaller(uint amount) public payable
{
}
// Create some new tokens, and give them to this TokenManager
function mint(uint amount) internal onlyOwner
{
}
// Destroy some existing tokens, that are owned by this TokenManager
function melt(uint amount) external onlyOwner
{
}
}
contract Test
{
function TestBuyRemit() payable public returns (uint)
{
TokenManager tok1 = new TokenManager(100,1);
TokenHolder h1 = new TokenHolder(tok1);
uint amt = 2;
tok1.sellToCaller{value:tok1.price(amt) + tok1.fee(amt)}(address(h1),amt);
assert(tok1.balanceOf(address(h1)) == amt);
h1.remit{value:tok1.fee(amt)}(1,50,tok1);
assert(tok1.balanceOf(address(h1)) == 1);
assert(tok1.balanceOf(address(tok1)) == 1);
return tok1.price(1);
}
function TestHolderTransfer() payable public returns (uint)
{
TokenManager tok1 = new TokenManager(100,1);
TokenHolder h1 = new TokenHolder(tok1);
TokenHolder h2 = new TokenHolder(tok1);
uint amt = 2;
tok1.sellToCaller{value:tok1.price(amt) + tok1.fee(amt)}(address(h1),amt);
assert(tok1.balanceOf(address(h1)) == amt);
h1.putUpForSale(2, 200);
h2.buy{value:2*202}(1,202,h1);
h2.buy(1,202,h1); // Since I loaded money the first time, its still there now.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment