Erc-20
Solidity 0.5.2 - OpenZeppelin - 新的 SafeERC20 callOptionalReturn(token,data)
我一直在嘗試將 ERC20 功能與 ERC223 功能重載技術混合,並使用 SafeERC20 創建向後兼容的橋樑。
為了相關性,我將 SafeERC20 重命名為 SafeERC223,但問題是,由於它僅適用於 ERC20,它只希望我們始終擁有 1 個傳遞函式,而不是像 ERC223 案例中的 2 個。
到目前為止我們有什麼:
- 地址庫
- SafeERC223 庫
- ERC223介面合約
- ERC223Receiving Contract // ERC223 tokenFallback() 發射器函式
- ERC223Token 合約
- ERC223合約
新的 SafeERC20 更酷的方法為 ERC223 函式重載技術帶來了一個有趣的挑戰,因為它只需要 1 個傳遞函式
Code
pragma solidity ^0.5.2; /** * Utility library of inline functions on addresses */ library Address { /** * Returns whether the target address is a contract * @dev This function will return false if invoked during the constructor of a contract, * as the code is not actually created until after the constructor finishes. * @param account address of the account to check * @return whether the target address is a contract */ function isContract(address account) internal view returns (bool) { uint256 size; // XXX Currently there is no better way to check if there is a contract in an address // than to check the size of the code at that address. // See https://ethereum.stackexchange.com/a/14016/36603 // for more details about how this works. // TODO Check this again before the Serenity release, because all addresses will be // contracts then. // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } } /** * @title SafeMath * @dev Unsigned math operations with safety checks that revert on error. */ library SafeMath { /** * @dev Multiplies two unsigned integers, reverts on 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); return c; } /** * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two unsigned integers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0); return a % b; } } contract ERC223Interface { function totalSupply() external view returns (uint256); function balanceOf(address who) external view returns (uint256); function transfer(address to, uint256 value) external returns (bool); function transfer(address to, uint256 value, bytes calldata data) external returns (bool); function approve(address spender, uint256 value) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function transferFrom(address from, address to, uint256 value) external returns (bool); function increaseAllowance(address spender, uint256 addedValue) external returns (bool); function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Transfer(address indexed from, address indexed to, uint256 value, bytes data); event Approval(address indexed owner, address indexed spender, uint256 value); } /** * @title SafeERC223 * @dev Wrappers around ERC223 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC223 for ERC223;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC223 { using SafeMath for uint256; using Address for address; function safeTransfer(ERC223Interface token, address to, uint256 value) internal { require(token.transfer(to, value)); // this method works but i'm not sure if it's safe to use } function safeTransfer(ERC223Interface token, address to, uint256 value) internal { callOptionalReturn(token, abi.encodeWithSelector(msg.sig, to, value)); // this method does not work when a contract executes safeTransfer } function safeTransferFrom(ERC223Interface token, address from, address to, uint256 value) internal { callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } function safeApprove(ERC223Interface token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' require((value == 0) || (token.allowance(address(this), spender) == 0)); callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance(ERC223Interface token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).add(value); callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance(ERC223Interface token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).sub(value); callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function callOptionalReturn(ERC223Interface token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. // A Solidity high level call has three parts: // 1. The target address is checked to verify it contains contract code // 2. The call itself is made, and success asserted // 3. The return value is decoded, which in turn checks the size of the returned data. require(address(token).isContract()); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = address(token).call(data); require(success); if (returndata.length > 0) { // Return data is optional require(abi.decode(returndata, (bool))); } } } contract ReentrancyGuard { using SafeMath for uint256; uint256 private _guardCounter; constructor () internal { _guardCounter = 1; } modifier nonReentrant() { _guardCounter = _guardCounter.add(1); uint256 localCounter = _guardCounter; _; require(localCounter == _guardCounter); } } contract ERC223ReceivingContract { /** * @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, uint256 _value, bytes memory _data) public; } contract ERC223Token is ERC223Interface { using SafeMath for uint256; address private _owner; // i know it's not really private :)) but clogs remix compiler :) // https://medium.com/swlh/ethereum-aint-hiding-your-secrets-703e89088937 string public constant name = "ERC223"; string public constant symbol = "ERC223"; uint8 public constant decimals = 18; uint256 private constant _totalSupply = 10000000 * (uint256(10) ** decimals); mapping (address => uint256) private _balances; mapping (address => mapping (address => uint256)) private _allowed; constructor() public { _owner = msg.sender; _balances[_owner] = _totalSupply; emit Transfer(address(0), _owner, _totalSupply); } function totalSupply() public view returns (uint256) { return _totalSupply; } function balanceOf(address owner) public view returns (uint256 balance) { return _balances[owner]; } function transfer(address to, uint256 value) public returns (bool success) { require(to != address(0)); require(value > 0 && balanceOf(msg.sender) >= value); require(balanceOf(to).add(value) > balanceOf(to)); uint256 codeLength; bytes memory empty; assembly { codeLength := extcodesize(to) } _balances[msg.sender] = _balances[msg.sender].sub(value); _balances[to] = _balances[to].add(value); if(codeLength>0) { ERC223ReceivingContract receiver = ERC223ReceivingContract(to); receiver.tokenFallback(msg.sender, value, empty); } emit Transfer(msg.sender, to, value, empty); return true; } function transfer(address to, uint256 value, bytes memory data) public returns (bool success) { require(to != address(0)); require(value > 0 && balanceOf(msg.sender) >= value); require(balanceOf(to).add(value) > balanceOf(to)); uint256 codeLength; assembly { codeLength := extcodesize(to) } _balances[msg.sender] = _balances[msg.sender].sub(value); _balances[to] = _balances[to].add(value); if(codeLength>0) { ERC223ReceivingContract receiver = ERC223ReceivingContract(to); receiver.tokenFallback(msg.sender, value, data); } emit Transfer(msg.sender, to, value, data); return true; } function approve(address spender, uint256 value) public returns (bool success) { _allowed[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); return true; } function allowance(address owner, address spender) public view returns (uint256) { return _allowed[owner][spender]; } function transferFrom(address from, address to, uint256 value) public returns (bool success) { require(to != address(0)); require(value <= _balances[from]); require(value <= _allowed[from][msg.sender]); _balances[from] = _balances[from].sub(value); _balances[to] = _balances[to].add(value); _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); emit Transfer(from, to, value); return true; } function increaseAllowance(address spender, uint256 addedValue) public returns (bool success) { _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add(addedValue); emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); return true; } function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool success) { uint256 oldValue = _allowed[msg.sender][spender]; if (subtractedValue > oldValue) { _allowed[msg.sender][spender] = 0; } else { _allowed[msg.sender][spender] = oldValue.sub(subtractedValue); } emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); return true; } function unlockERC20Tokens(address tokenAddress, uint256 tokens) public returns (bool success) { require(msg.sender == _owner); return ERC223Interface(tokenAddress).transfer(_owner, tokens); } function () external payable { revert("This contract does not accept ETH"); } } contract ERC223Contract is ReentrancyGuard { using SafeMath for uint256; ERC223Interface private token; constructor(ERC223Interface _token) public { token = _token; emit Created("Successfully created ERC223 Contract"); } function getBlockNumber() public view returns (uint256) { return block.number; } function getData() public pure returns (bytes memory) { return msg.data; } function getSignature() public pure returns (bytes4) { return msg.sig; } function () external { //if ether is sent to this address, send it back. revert(); } function tokenFallback(address player, uint tokens, bytes memory data) public nonReentrant { require(msg.sender == address(token)); emit DepositedERC223Token(player, tokens, data); } event Created(string); event DepositedERC223Token(address from, uint value, bytes data); } contract ICO is ReentrancyGuard, Owned{ using SafeMath for uint256; using SafeERC223 for ERC223Interface; // TOKEN INTERFACE, ERC223Interface private token; // ICO WALLET address payable private wallet; // TOTAL ETHER RAISED uint public totalRaised; // TOTAL TOKENS DISTRIBUTED uint public tokensSold; // KYC VALIDATION, ADDRESS DEPOSIT LIMIT mapping(address => bool) private _kyc; mapping(address => uint) public senderLimit; event TokensPurchased(address indexed purchaser, uint256 value, uint256 amount); constructor(ERC223Interface _token, address payable _wallet) public { require(address(_token) != address(0)); require(address(_wallet) != address(0)); token = _token; wallet = _wallet; } // GET OWNER ADDRESS function tokenOwnerAddress() public view returns(address) { return token.getOwner(); } // GET ICO ADDRESS function icoAddress() public view returns(address) { return address(this); } // GET CONTRACT TOTAL TOKENS LEFT FOR SALE function checkBalance() public view returns(uint) { return token.balanceOf(address(this)); } // KYC - VALIDATE OR INVALIDATE ADDRESS function updateKYC(address userAddress, bool status) public onlyOwner returns(bool) { _kyc[userAddress] = status; return status; } // KYC - CHECK IF ADDRESS HAS BEEN VALIDATED function checkKYC(address userAddress) public view returns(bool) { return _kyc[userAddress]; } // PAYABLE FUNCTION function () external payable { // buyTokens(); buyTokens(msg.sender); } //---------------------------------------------------- // 1,000 Tokens == 1 ETH => 1 ETH + 50% BONUS = 1,500 LVC //---------------------------------------------------- function buyTokens(address beneficiary) public nonReentrant payable { require(checkBalance() != 0); require(msg.sender == beneficiary); uint256 weiAmount = msg.value; uint tokens; // PRESALE - 50% BONUS tokens = weiAmount * 1500; tokensSold = tokensSold.add(tokens); totalRaised = totalRaised.add(weiAmount); _processPurchase(beneficiary, tokens); _forwardFunds(); senderLimit[beneficiary] = senderLimit[beneficiary].add(weiAmount); if(_kyc[beneficiary] == false){ require(weiAmount >= .1 ether && weiAmount <= 25 ether && senderLimit[beneficiary] <= 25 ether); } else if (_kyc[beneficiary] == true) { require(weiAmount >= .1 ether && weiAmount <= 100 ether && senderLimit[beneficiary] <= 100 ether); } emit TokensPurchased(beneficiary, weiAmount, tokens); } function _deliverTokens(address beneficiary, uint256 tokenAmount) internal { token.safeTransfer(beneficiary, tokenAmount); } function _processPurchase(address beneficiary, uint256 tokenAmount) internal { _deliverTokens(beneficiary, tokenAmount); } function _forwardFunds() internal { wallet.transfer(msg.value); } function _transferToBonusReserve(address beneficiary, uint256 tokenAmount) internal { _deliverTokens(beneficiary, tokenAmount); } function unlockBlockedERC20Tokens(address tokenAddress, uint tokens) public onlyOwner returns (bool success) { return ERC223Interface(tokenAddress).transfer(owner, tokens); } function tokenFallback(address owner, uint tokens, bytes memory data) public nonReentrant { require(msg.sender == address(token)); require(owner == tokenOwnerAddress()); emit DepositedTokens(owner, tokens, data); } event DepositedTokens(address from, uint value, bytes data); }
如果我不從 ERC223Interface 註釋掉第二個傳遞函式,這就是我得到的錯誤。
TypeError: Member "transfer" not unique after argument-dependent lookup in contract ERC223Interface.callOptionalReturn(token, abi.encodeWithSelector( token.transfer.selector, to, value));
更新
- 檢查 SafeERC223 - 在舊的 SafeERC223 工作時拋出 msg.sig…
- 添加了一個 Crowdsale 範例,您可以在其中測試它是如何失敗的
重現步驟:
部署 ERC223 代幣
- 部署 ERC223 代幣
- 使用 2 個參數(代幣地址、ETH 錢包)部署 ERC223 ICO
- 所有者將總供應量的一半轉移到 ICO 契約。
- 貢獻者發送 1 個 ETH 並應收到 1500 個代幣。此步驟使用 SafeERC223 並且應該將令牌轉移給貢獻者,但如果我們使用 msg.sig 它會失敗,如果我們只使用舊的做事方式,但我不知道它是否可以安全使用。
提示: SafeTransfer 在 _processPurchase() 中使用 如果我們切換註釋 msg.sig 方法並取消舊的 SafeTransfer 它應該像以前一樣工作,但我不知道這種方式有多安全。
乾杯!
如果我沒記錯的話,問題是
token.transfer.selector
試圖從原始碼中找出簽名。這將相當於
abi.encodeWithSelector(bytes4(keccak256(bytes(signature)));
以這種方式不可能進行區分,因為您想要“兩者”。
您對交易附帶的函式簽名感興趣。您應該希望它們完美地映射到實施契約。如果我的想法是正確的,那麼將其挑出來
msg.sig
並通過就足夠了。那會給你留下這個:
function safeTransfer(ERC223Interface token, address to, uint256 value) internal { callOptionalReturn(token, abi.encodeWithSelector(msg.sig, to, value)); }
沒有保修 ;-)
希望能幫助到你。