零碎的Solidity小Tip

  • Tiny熊
  • 更新于 2022-06-17 16:39
  • 阅读 47

本文作者:shenstone.eth 1.How would you call a function on a third party contract B, on behalf of the sender, through your contract A, guaranteeing no reverts?

解析: 题目意思...

本文作者:shenstone.eth 1.How would you call a function on a third party contract B, on behalf of the sender, through your contract A, guaranteeing no reverts?

解析: 题目意思就是如何通过合约 A 代表发送者调用合约 B 的函数,答案很简单咯,就是delegatecall。

contract B{ uint value; function test(){ value = 10; } }

contract A{ function _delegate(address _B){ (bool suc,) = _B.delegatecall(abi.encodeWithSignature("test")); require(suc, "delegate call failed"); } } 2.What’s the main difference between a transparent proxy and a universal proxy?

解析: 题目意思是透明合约和代理合约的主要区别是啥?社区里这篇文章很详细的介绍了这俩代理模式的。可以瞅一瞅。

contract TransparentProxy{ uint public value; address public implementation; address public owner; constructor(address _v1){ implementation = v1; owner = msg.sender; } modifier onlyOwner(){ require(msg.sender == owner, "only owner"); ; } function _upgrade(address _newImplementation) public onlyOwner{ implementation = _newImplementation; }

function _delegatecall() public {
    (bool suc,) = implementation.delegatecall(msg.data);
    require(suc, "delegatecall failed");
}

}

contract BoxV1{ uint public value; function set(uint _value) public{ value = _value; } }

contract UniversalProxy{ uint public value; address public implementation; constructor(address _v1){ implementation = _v1;

}
function _delegatecall() public {
    (bool suc,) = implementation.delegatecall(msg.data);
    require(suc, "delegatecall failed");
}

}

abstract contract Box{ uint public value; address public implementation; address public owner ; modifier onlyOwner(){ require(msg.sender == owner, "only owner"); _; } function _upgrade(address _newimplementation) public onlyOwner { implementation = _newimplementation; } } contract BoxV1 is Box{ function set(uint _value) public { value = _value; } } 通用代理优势:通过在实现合约上定义所有函数,它可以依靠 Solidity 编译器检查任何函数选择器冲突。此外,通用代理的大小要小得多,从而使部署更便宜。在每次调用中,从存储中需要读取的内容更少,降低了开销。 通用代理劣势:如果一次合约忘记写 upgrade 函数,将会永远无法升级了。

3.How could you destroy the implementation of, and effectively brick, a universal proxy? > Assumptions: > > - You re not the owner of the proxy > - No one owns the implementation > - The implementation uses delegatecall to guarantee that the next implementation is not sterile

解析: 题目大概意思是,如何毁掉一个通用代理下的实现合约?条件如下:

你不是代理合约的 owner 没人是 implementation 合约的 owner implementation 合约使用了 delegatecall 去保证每个 implementation 合约总是可升级的(可以看上面的通用代理劣势) 因为 implementation 合约里有调用 delegatecall,去调用别的合约,那只要在该合约中加一个 selfdestruct 函数,即可完成对通用代理的毁灭。所以在 implementation 合约里最好不要使用 selfdestruct 和 delegatecall 函数。详细可以查看这篇文档

4.What’s the danger of using tx.origin for user authentication in a smart contract?

解析: tx.origin 是最开始调用合约的 EOA 账户(外部账户),如果你的合约 A 用了 tx.origin 来判断身份的话,最好就别执行了陌生的合约了,因为可能该合约会调用合约 A,来转走你的钱。 如果你没看懂我在说啥的话,这里提供个案例去参考下。

5.What kind of proxy would you use to update an indefinite amount of instances with a single implementation upgrade? And how would it work?

解析: 题目大概意思是:什么样的代理可以让你只要升级一次实现,完成多个实例代理呢? 使用信标代理,信标代理特征,再加一层合约,其保存着 implementation,代理合约存着信标合约地址。示例如下:

contract Proxy { address immutable beacon = 0xaaaaaaaaaaaaaaaaaaaaa; fallback() external payable{ address implementation = beacon.implementation(); return implementation.delegatecall(msg.data); } }

contract Beacon{ address public implementation; function upgrade(address _newImplementation) public onlyOwner{ implementation = _newImplementation; } } 6.Whats the deal with external vs public? When should you use external? When not? Why is it cheaper than public?

解析: External 函数一般是外部合约或者 EOA 账户调用,如果是合约内部其他函数调用,需要使用方法http://this.xxx(),Public 函数外部内部都可以调用。 External 函数更便宜是因为它的参数可以直接从 calldata 里取出,而 Public 函数则需要先把他们加载到 Memory 里,这会消耗更多的 gas。 另外注意使用http://this.xxx()时,会改变 msg.sender,具体如下:

//EOA address: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 call A.callFunc() contract A{ address immutable caller = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; function callFunc() public{ testPublic(); this.testExternal(); }

function testPublic() public{
    require(caller == msg.sender, "when u use Public, the msg.sender is still the caller before");
}

function testExternal() external{
    require(address(this) == msg.sender, "when u use Exteranl, the msg.sender is the contract self");
}

} 7.3rd party contract B with function b() writes to state in ways outside of your control. How would you simulate a call to b() from your contract A, observe the side effects, and then undo them entirely without reverting the main execution thread?

解析: 题目大概意思是:如何调用第三方合约的函数 b(),并保证不会干扰你的主要执行线程。 使用 try...catch...,下面举个例子,安全转账 ERC20。

contract B is ERC20{ function transferFrom(address _sender, address _to, uint _amount) public returns (bool){ //... } }

contract A{ function interactWithToken(uint _amount){ IERC20 token = IERC20(tokenAddress); bool success;

    try token.transferFrom(msg.sender, address(this), sendAmount)returns(bool _success){
        success = _success;
    } catch Error(string memory) {
        success = false;
    } catch (bytes memory) {
        sucess = false;
    }

    if(success) {
        // handle success case
    } else {
        // handle failutre case without reverting
    }
}

} 8.Dynamically sized types in function signatures may be preceded by the keywords “memory”, “storage”, or “calldata”. When is it optimal to use each of them?

解析: 题目大概意思是函数参数中的动态类型对象可以注明关键词"memory","storage","calldata",分别什么情况下使用。 calldata 基本是为 external 函数服务的。它跟 memory 很像,只是是不可更改的,memory 是可更改的。storage 类型只能在 internal 函数中传递,并作为引用传送,这样才能写入。

9.Is there a way to revert with dynamic error messages? I.e. “Error: Price must be > 1 ETH”, where 1 is a value held in a state variable in your contract.

解析: 如下所示:

contract example{ function test() public{ uint8 value = 1; string memory tmp = string(abi.encodePacked("Error:Price must be", value, " ETH")); require(false, tmp);

}

} 10.Bob calls contract A, which delegate calls contract B, which delegate calls contract C, which calls contract D, which delegate calls contract E, which delegate calls contract A.Who is msg.sender when the execution reaches back contract A?

解析:

when Bob call A, sender is EOA when A dcall B, sender is EOA when B dcall C, sender is EOA when C call D, sender is C now when D dcall E, sender is C when E dcall A, sender is C The ans is C. 11.When you compile a Solidity contract, you get "bytecode" and "deployedBytecode". They are almost identical. What's the difference? Where is the difference? And why is there a difference?

解析: 题目大概意思是,当你编译一个 solidity 合约时,你会得到"bytecode"和"deployedBytecode",它们俩基本一致,区别在哪,为啥会有这个区别?

bytecode 是发送给地址 0 的 msg.data,里面包含部署合约的字节码,它们将执行合约的构造函数,以及把合约代码(也就是 deployedBytecode)写入 state 里。

12.Smart contract A’s view function a() needs to call a third party contract B’s b() function, which is also supposed to be view. Can it guarantee that it will really be read only too by just calling it, or does it need to take any additional precautions?

解析: 题目大概意思是,合约 A 的 view 函数 a()调用合约 B 的函数 b(),为了调用成功 b()也得是 view。如何保证调用的函数是只读的呢?还是需要额外的防护措施。这里可以采用 staticcall,以及对执行结果进行判断。

contract A { B public b;

constructor() {
    b = new B();
}

function callB() public view returns(string memory) {
    (bool success, bytes memory data) = address(b).staticcall(
        msg.data
    );

    if(!success) revert();

    return string(data);
}

} 13.Does adding or changing comments on a contract affect its resulting runtime bytecode?

解析: 会有影响,会改变 metadata 值。

14.To index or not to index, that is the question. Does using indexed in events increase runtime gas costs? How about bytecode size?

解析: 事件的 gas 费公式如下:

375 + 375 numberOfIndexedParameters + numberOfUnindexedBytes 8. 比较下列 gas 费:

  • event NewEvent(address addr)
  • event NewEvent(address indexed addr)
  • 375 + 375 0 + 208 = 535
  • 375 + 375 1 + 08 = 750 可以看出,indexed 事件消耗更多 gas。

The indexed logs contract is just 370 bytes, while the unindexed log contract is 516 bytes. This is because the log opcodes in the evm load the values from memory, which require first loading the local variable from the stack into memory. Meanwhile indexed logs are loaded directly from the stack, which does not have the overhead of moving the variable into memory.

15.A contract contains an array of one million addresses, and a view function that simply returns this array. Will this function run out of gas when: > - Called by a contract? > - Called externally by an EOA?

解析: 不论是谁调用 view 函数,都会计算 gas。只是如果只是查询状态,不会真的扣钱。

contract A{ uint public value; constructor()payable{ value = 1; } function test() external view returns(uint){ return value; //不消耗gas } function test2() public{ address _to = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; uint _tmp = this.test(); //消耗 gas,因为该函数有修改状态 (bool success,) = _to.call{value: _tmp}(""); require(success,"failed"); }

function test3() external view returns(uint){ return this.test(); //不消耗gas } } 16.You have a public view function in a contract. Can it know whether it's been called as part of a transaction that mutates the root hash of the chain, as opposed to just a simple read call?

解析: 这题大概意思是如何防止你的用户调用你的 view 函数付出了 gas 费。 看老外做了个分析。 不同的平台 EOA 调用 view 函数时,tx.gasprice 和 tx.origin 分别是多少

  • 学分: 0
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Tiny熊
Tiny熊
0x1231...6564
登链社区发起人 登链团队对 DEFI 应用有深刻的理解和丰富的开发经验,如果你有开发、审计、培训合作等需求, 加我微信:xlbxiong 。 咨询问题在问答区提问即可,微信好友太多,不看问题,请凉解~