Solidity

我的契約有什麼漏洞?

  • December 28, 2019

我在主網上部署了一份合約0xe559835D7979dAe994331DEfaEc2FBA057E56C27

每次我去呼叫這個payable函式時solve,一個機器人都會打敗我。我的交易被拒絕,他們的交易通過了。

pragma solidity >=0.4.21 <0.6.0;

// deployed at 
// 0xe559835d7979dae994331defaec2fba057e56c27

contract EqHunt {

 struct Equation {
   string repr;
   int answer;
   bool exists;
 }

 mapping(string => Equation) equations;
 mapping(string => address[]) public solvers;
 address payable private owner;

 uint256 public lastSolveTime;

 uint256 private delay;

 constructor() public payable {
   owner = msg.sender;
   lastSolveTime = now;
   delay = 10 minutes;
 }

 function locked() public view returns(bool) {
   if(now - lastSolveTime <= delay) {
     return true;
   } else {
     return false;
   }
 }

 function create(string memory _id, string memory _repr, int _answer) public {

   require(msg.sender == owner);

   require(!equations[_id].exists);

   Equation memory e = Equation(_repr, _answer, true);

   equations[_id] = e;

 }

 function getEquation(string memory _id) public view returns(string memory) {
   return equations[_id].repr;
 }

 function check(string memory _id, int _answer) internal view returns(bool) {

   require(equations[_id].exists);

   if(equations[_id].answer == _answer) {
     return true;
   } else {
     return false;
   }
 }

 function rand() internal view returns(uint256) {
   return uint256(uint256(keccak256(abi.encode(block.timestamp)))%10) + 1;
 }

 function payout() internal view returns(uint256) {
   return 40000000000000000/rand();
 }

 function reward(address payable _payee) internal {

   uint256 r = payout();

   require(address(this).balance>=r);

   require(now - lastSolveTime > delay);

   _payee.transfer(r);

   lastSolveTime = now;

 }

 function solve(string memory _id, int256 _answer) public payable {
   require(!hasSolved(_id));
   bool correct = check(_id, _answer);
   require(correct);
   if(correct) {
     addSolver(_id);
     reward(msg.sender);
   }
 }

 function addSolver(string memory _id) internal {
   solvers[_id].push(msg.sender);
 }

 function hasSolved(string memory _id) internal view returns(bool) {
   address[] memory _solvers = solvers[_id];
   for(uint256 i=0; i<_solvers.length; i++) {
     if(_solvers[i] == msg.sender) {
       return true;
     }
   }
   return false;
 }

 function getNumSolvers(string memory _id) public view returns(uint256) {
   address[] memory _solvers = solvers[_id];
   return _solvers.length;
 }

 function getSolvers(string memory _id) public view returns(address[] memory) {
   return solvers[_id];
 }

 function withdraw() public payable {
   require(msg.sender == owner);
   owner.transfer(address(this).balance);
 }

 function() payable external {}

 // // TEST CODE
 //
 // function testRand() public view returns(uint256) {
 //   return rand();
 // }
 //
 // function testPayout() public view returns(uint256) {
 //   return payout();
 // }
 //
 // function testReward(address payable _payee) public {
 //   lastSolveTime -= 10 minutes;
 //   reward(_payee);
 // }
 //
 // function getBalance() public view returns (uint256) {
 //   return address(this).balance;
 // }
 //
 // function testHasSolved(string memory _id) public view returns(bool) {
 //   return hasSolved(_id);
 // }
 //
 // function testCheck(string memory _id, int _answer) public view returns(bool) {
 //   return check(_id, _answer);
 // }

}

我希望真實的人與契約互動,而不是機器人——我認為使用 4 個字元的 ID 可以防止這種情況。

聽起來你在搶先一步。這意味著機器人正在監視您發送交易,讀取輸入(特別是_answer),並送出具有正確答案但更高的交易gasPrice。礦工會在你之前接受他們的交易,導致他們的成功而你的失敗。

您可以通過觀察您的交易攻擊者的交易來看到這實際上是正在發生的事情。你會看到攻擊者使用了 5 Gwei,gasPrice而你只使用了 3。礦工將首先接受他們的交易,因為礦工將獲得更高的 tx 費用。

為了避免這種情況,您將不得不重新設計您的契約。您可以使用commit-reveal方案或類似的方法來解決此問題。

引用自:https://ethereum.stackexchange.com/questions/78578