初始化
创建了一个Creature的类,加以调用,这个部分没啥
1 | // SPDX-License-Identifier: UNLICENSED |
需要关注的的是这个类
1 | Creature.sol |
这里它给一个变量赋值了1000,也就是游戏里怪物的血量,在初始化类中它直接调用的这里创建了一个1000hp的怪物。
1 | constructor() payable { |
然后是攻击部分,这里函数允许接受一个uint256的数字变量
这里在初次调用attack()
时,首先判断aggro是否为空,如为空则将第一个攻击函数调用者的地址赋予它。
这里可以把aggro看作是生物的仇恨目标,谁先发起攻击,他就一直仇恨谁。
在之后的判断的逻辑中需要满足,后来攻击者的地址不能等于第一个攻击者的地址。
1 | function attack(uint256 _damage) external { |
其次是主要攻击部分,这里引入了一个_isOffBalance()函数返回布尔,当达成条件时
即可让lifePointes减少对应的hp也就是_damage变量lifePoints -= _damage
1 | if (_isOffBalance() && aggro != msg.sender) { |
当满足_isOffBalance()以及用正常地址调用合约时,便可减少怪物血量
所以接下来看_isOffBalance()是如何构造的
1 | function _isOffBalance() private view returns (bool) { |
他在这里用tx.origin != msg.sender
做对比,当两个值不相等时便返回true
这里的tx.origin
是合约初始调用地址
而msg.sender
则是最近发送者的合约地址。
用一个简单的合约调用链来看下,方便理解
EOA —> 合约A —>合约B
这里EOA是我们的原始地址
举个例子,如果我们的原始合约地址是0x0010
当在调用合约A时,合约A里两个变量:
msg.sender=0x0010
tx.origin=0x0010
此时如果我们通过合约A再调用合约B
在合约B中看两个变量:
1 |
|
可以发现在合约B中的msg.sender变成了合约A的地址,msg.sender代表的是最近的调用者合约地址
而tx.origin则是不会变的,依旧是EOA最初的地址,tx.origin代表的就是原始合约地址
这是两者最显著的区别。
再回过头去看这个函数,其实就是原始调用地址不能等于最近调用者的合约地址。
1 | function _isOffBalance() private view returns (bool) { |
简单的说,不可以直接用原始合约来调用这个合约
要用中间在做一层合约,去调用这个合约,即刻达成返回true,就像下面这样
EOA —> 合约A —>合约B
为此我们需要构造一个合约来利用
除此之外,我们还需要关注之前的aggro != msg.sender
也要满足
至此我们现在需要满足以下几个条件,即可发起攻击:
1.为满足
aggro != msg.sender
,我们需要先用一个诱饵地址去吸引仇恨2.在满足1的前提下,为满足
tx.origin != msg.sender
须通过一个新合约去调用这个游戏的合约。
可能有师傅条件一多就有点混了,会想用户和合约中继分别发起攻击,不分前后顺序可不可以?
答案是不行
原因是,虽然不分前后顺序确实都可以满足aggro != msg.sender
,但是却无法满足tx.origin != msg.sender
如果首先发起攻击请求的是中继合约
1.aggro目标指向了中继合约用户,然后aggro != msg.sender
false
2.再EOA发起,aggro != msg.sender
True,但是这里tx.origin != msg.sender
会false,因为首先tx.origin
代表的合约原始地址,msg.sender
则是距离游戏合约最近一个调用的地址,这里因为本次调用者是EOA,导致原始调用是EOA的同时距离最近调用者也是EOA。所以俩值一毛一样了..
如果顺序反过来,由EOA先调用,再调用中继合约
1.aggro目标指向了EOA用户,然后aggro != msg.sender
false
2.再用中继合约,tx.origin != msg.sender
,tx.origin
等于原始调用者EOA,msg.sender
等于中继合约,于是True,所以可以发起攻击。
中继合约构造
1.首先我们要构造一个基于本游戏合约的中继合约需要引入他的对象,其实有点类似于java那种引入一个外部库的类,然后用地址将其实例化。
我们先看下游戏自己的sol咋写的
1 | // SPDX-License-Identifier: UNLICENSED |
然后我是这么写的,引入Creature.sol的Creature合约对象,然后给新合约起个名叫flower(
1 | └─$ cat src/attc.sol |
给了一个attck让他来执行攻击,这里再加个loot也行,但是我懒
合约构造与使用
1.首先用forge初始化一下合约目录
1 | └─$ ~/tools/blockchain/forge init --force |
可以看到当前目录下多了不少文件夹
1 | └─$ ls |
把我们的.sol和Creature.sol一起丢到./src目录下
2.在链上线合约
用游戏给我们的私钥进行签名认证。然后在将target address作为Creature _creature
的生成初始化变量传入到里面的对象,这样我们的合约内的对象就与游戏的对象连到了一起。
1 | forge create src/attc.sol:flower --rpc-url "http://94.237.52.48:35311/rpc" --private-key 0x8a277e85b81b9f66613490f2ba53a40bce5a65f6aecce9e06f3f59aa48ec271c --constructor-args 0x276B1607C79025D125E010740cA3ECD3656F9C54 |
同时还会得到我们自己合约在链上的地址与哈希
Deployer: 部署者地址,即发起部署操作的账户地址。在这里是 >0x0B0c991073613cF3D49cf8360696F9046aEc871f。
Deployed to: 合约部署地址,即智能合约在区块链上的部署地址。在这里是 >0xF68da11A8582ba729e9d8724a4d02718D5fd5207。
Transaction hash: 交易哈希,即进行合约部署操作的交易的哈希值。它用于在区块链上查找该交易的详情和状态。在这里>0x93d17dcc93173ea6c3390c25ee6e44930c5b602d90727bbc18af92271939f7b0
调用部分
1.先用EOA,也就是用户直接对目标地址发起请求
1 | cast send "0x6a1A3839D25AD2A31Ed0c1d5Cbc10e08958Bb05f" --rpc-url http://94.237.52.48:35311/rpc --private-key 0xf6235a58909371d81b25c276be0fe1f5e66dbf2a8ee15a25476c4139bb56e87e "attack(uint256)" 0 |
2.再调用我们的合约,对我们上线的合约发起请求
1 | cast send "0xF68da11A8582ba729e9d8724a4d02718D5fd5207" --rpc-url http://94.237.52.48:35311/rpc --private-key 0xf6235a58909371d81b25c276be0fe1f5e66dbf2a8ee15a25476c4139bb56e87e "attackA(uint256)" 1000 |
3.没报错的话就是调用成功了,到此就可以调用游戏的合约的loot()结算了
1 | cast send "0x6a1A3839D25AD2A31Ed0c1d5Cbc10e08958Bb05f" --rpc-url http://94.237.52.48:35311/rpc --private-key 0xf6235a58909371d81b25c276be0fe1f5e66dbf2a8ee15a25476c4139bb56e87e "loot()" |