如何限制令牌接收者调用者接受的令牌地址?

Ale*_*hin 10 solidity rsk hardhat

我想创建一个包含功能的应付令牌transferAndCall(TokenReceiver to, uint256 amount, bytes4 selector)。通过调用该函数,您可以将代币转移到TokenReceiver智能合约地址,然后调用接收者,接收者又调用接收者onTransferReceived(address from,uint tokensPaid, bytes4 selector)中指定的函数。bytes4 selector

请注意,这与 ERC1363 类似/受到ERC1363的启发。

这是我的应收令牌的简化版本:

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MeowToken is ERC20 {
  constructor() ERC20("MeowToken", "MEO") {
    ERC20._mint(msg.sender, 10_000_000);
  }
  function transferAndCall(
    TokenReceiver to,
    uint256 amount,
    bytes4 selector
  ) external {
    ERC20.transfer(address(to), amount);
    to.onTransferReceived(msg.sender, amount, selector);
  }
}
Run Code Online (Sandbox Code Playgroud)

这是一个令牌接收器:

contract TokenReceiver {
  address acceptedToken;
  event PurchaseMade(address from, uint tokensPaid);
    
  modifier acceptedTokenOnly () {
    require(msg.sender == address(acceptedToken), "Should be called only via the accepted token");
    _;
  }
    
  constructor(address _acceptedToken) {
    acceptedToken = _acceptedToken;
  }
    
  function onTransferReceived(
    address from,
    uint tokensPaid,
    bytes4 selector
  ) public acceptedTokenOnly {
    (bool success,) = address(this).call(abi.encodeWithSelector(selector, from, tokensPaid));
    require(success, "Function call failed");
  }

  function purchase(address from, uint tokensPaid) public acceptedTokenOnly {
    emit PurchaseMade(from, tokensPaid);
  }
}
Run Code Online (Sandbox Code Playgroud)

我想确保接收器上的公共函数通过应付令牌调用。因此我acceptedTokenOnly给它们都添加了修饰符。然而,添加修改器后,我的测试开始失败:

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MeowToken is ERC20 {
  constructor() ERC20("MeowToken", "MEO") {
    ERC20._mint(msg.sender, 10_000_000);
  }
  function transferAndCall(
    TokenReceiver to,
    uint256 amount,
    bytes4 selector
  ) external {
    ERC20.transfer(address(to), amount);
    to.onTransferReceived(msg.sender, amount, selector);
  }
}
Run Code Online (Sandbox Code Playgroud)
1) Transfer and call
       Transfer Tokens and call Purchase:
     Error: VM Exception while processing transaction: reverted with reason string 'Function call failed'
Run Code Online (Sandbox Code Playgroud)

为什么会出现这种情况?如何确保接收者的函数仅由接受的令牌调用?

作为参考,我正在 Hardhat 中开发和测试智能合约并在RSK上部署。

小智 9

当你这样做时:

(bool success,) = address(this).call(abi.encodeWithSelector(selector, from, tokensPaid));
Run Code Online (Sandbox Code Playgroud)

您正在拨打外部电话,这意味着它将msg.sender变成address(this).

acceptedTokenOnly现在,函数期间的修饰符purchase将失败,因为msg.sender不再是令牌。

建议将功能更改为:

  function purchase(address from, uint tokensPaid) public {
    require(msg.sender == address(this), "wrong sender");
    emit PurchaseMade(from, tokensPaid);
  }
Run Code Online (Sandbox Code Playgroud)


Jua*_*ola 7

问题是,您使用的是低级call方法,此处:\n\xe2\x80\x8b

\n
    (bool success,) = address(this).call(abi.encodeWithSelector(selector, from, tokensPaid));\n
Run Code Online (Sandbox Code Playgroud)\n

msg.sender\xe2\x80\x8b\n这会将inside的值onTransferReceived从接受的令牌更改为接收者本身。

\n

这是实现您想要的效果的一种方法:\n\xe2\x80\x8b\n替换calldelegatecall。\n这将立即解决您的问题。\n与 不同call,它将delegatecall代表调用者智能合约调用您的函数:\n\xe2 \x80\x8b

\n
  function onTransferReceived(\n    address from,\n    uint tokensPaid,\n    bytes4 selector\n  ) public acceptedTokenOnly {\n    (bool success,) = address(this).delegatecall(abi.encodeWithSelector(selector, from, tokensPaid));\n    require(success, "Function call failed");\n  }\n
Run Code Online (Sandbox Code Playgroud)\n


Gin*_*hon 5

除了从 切换calldelegatecall,正如 @Juan 的回答中提到的,还有一种更“手动”的方法:\n\xe2\x80\x8b\n不要call完全使用,而是按名称调用函数。\n这可以使用来完成if ... elseselector参数与预期功能选择器进行比较的控制结构 ( ) purchase:\n\xe2\x80\x8b

\n
  function onTransferReceived(\n    address from,\n    uint tokensPaid,\n    bytes4 selector\n  ) public acceptedTokenOnly {\n    if (selector == this.purchase.selector) {\n      purchase(from, tokensPaid);\n    } else {\n      revert("Call of an unknown function");\n    }\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\x8b\n虽然这样做比较繁琐,但从安全角度来看可能更可取。\n例如,如果您希望将允许通过此机制调用的函数列入\n白名单。 \n请注意,使用call/ 的方法delegatecall暴露了任意(可能是非预期)函数执行的潜在漏洞。

\n