終於到了要解析其轉賬的流程了。不過這裏我不用自己的賬號來做,因爲我暫時不想轉出來,那就用我好友的案例來分析吧。
下面是他自己的錢包最終受到0.01ETH轉賬的截圖
仔細看這筆交易的詳情
首先這個From地址是一個外部賬戶,這個To地址是TransferManager合約地址,而合約裏的from是他的錢包合約地址,to就是他的另一個以太坊錢包地址。
從這裏可以看出是通過外部賬號From調用TransferManager合約的某個接口來實現從錢包合約地址把ETH轉到自己指定的錢包地址的。
這下就很清楚了。當我在手機APP上填寫好轉賬信息,並點擊轉賬按鈕之後,實際上不是我直接控制我的錢包合約把ETH從錢包合約轉到我指定的以太坊錢包地址。而是間接的會觸發一箇中心化的服務,這個服務會調用外部賬號執行TransferManager合約的某個接口來完成我的這筆轉賬請求。
那調用的是TransferManager的哪個接口呢?
看來還得去Event log裏找。根據區塊高度,在TransferManager合約的Event log裏找到以下內容
下面看看這個execute函數的源碼:
function execute(
BaseWallet _wallet,
bytes calldata _data,
uint256 _nonce,
bytes calldata _signatures,
uint256 _gasPrice,
uint256 _gasLimit
)
external
returns (bool success)
{
uint startGas = gasleft();
bytes32 signHash = getSignHash(address(this), address(_wallet), 0, _data, _nonce, _gasPrice, _gasLimit);
require(checkAndUpdateUniqueness(_wallet, _nonce, signHash), "RM: Duplicate request");
require(verifyData(address(_wallet), _data), "RM: the wallet authorized is different then the target of the relayed data");
uint256 requiredSignatures = getRequiredSignatures(_wallet, _data);
if ((requiredSignatures * 65) == _signatures.length) {
if (verifyRefund(_wallet, _gasLimit, _gasPrice, requiredSignatures)) {
if (requiredSignatures == 0 || validateSignatures(_wallet, _data, signHash, _signatures)) {
// solium-disable-next-line security/no-call-value
(success,) = address(this).call(_data);
refund(_wallet, startGas - gasleft(), _gasPrice, _gasLimit, requiredSignatures, msg.sender);
}
}
}
emit TransactionExecuted(address(_wallet), success, signHash);
}
看到這裏只有一個 emit TransactionExecuted(address(_wallet), success, signHash);
那另外一個Transfer事件日誌是在哪裏?
仔細研究了一番,只有一行代碼有可能
(success,) = address(this).call(_data);
但是這裏的data是直接從鏈下傳進來的,現在無從得知。不過可以肯定的是,調用這個代碼會往Event log寫入Transfer日誌。搜遍源碼只有一個函數:
contract BaseTransfer is BaseModule {
...
function doTransfer(BaseWallet _wallet, address _token, address _to, uint256 _value, bytes memory _data) internal {
if (_token == ETH_TOKEN) {
invokeWallet(address(_wallet), _to, _value, EMPTY_BYTES);
} else {
bytes memory methodData = abi.encodeWithSignature("transfer(address,uint256)", _to, _value);
invokeWallet(address(_wallet), _token, 0, methodData);
}
emit Transfer(address(_wallet), _token, _value, _to, _data);
}
...
}
這裏核心代碼就幾行
if (_token == ETH_TOKEN) {
invokeWallet(address(_wallet), _to, _value, EMPTY_BYTES);
}
這裏的EMPTY_BYTES就是空字符串,定義如下:
bytes constant internal EMPTY_BYTES = "";
繼續往下dig
function invokeWallet(address _wallet, address _to, uint256 _value, bytes memory _data) internal returns (bytes memory _res) {
bool success;
// solium-disable-next-line security/no-call-value
(success, _res) = _wallet.call(abi.encodeWithSignature("invoke(address,uint256,bytes)", _to, _value, _data));
if (success && _res.length > 0) { //_res is empty if _wallet is an "old" BaseWallet that can't return output values
(_res) = abi.decode(_res, (bytes));
} else if (_res.length > 0) {
// solium-disable-next-line security/no-inline-assembly
assembly {
returndatacopy(0, 0, returndatasize)
revert(0, returndatasize)
}
} else if (!success) {
revert("BM: wallet invoke reverted");
}
}
看到下面這樣代碼
_wallet.call(abi.encodeWithSignature("invoke(address,uint256,bytes)", _to, _value, _data));
繼續往下dig
function invoke(address _target, uint _value, bytes calldata _data) external moduleOnly returns (bytes memory _result) {
bool success;
// solium-disable-next-line security/no-call-value
(success, _result) = _target.call.value(_value)(_data);
if (!success) {
// solium-disable-next-line security/no-inline-assembly
assembly {
returndatacopy(0, 0, returndatasize)
revert(0, returndatasize)
}
}
emit Invoked(msg.sender, _target, _value, _data);
}
終於到頭了,看到這句
(success, _result) = _target.call.value(_value)(_data);
這其實就是把_value這麼多ETH轉向_target這個地址。不過有點讓我很疑惑。看下圖
本來這裏是空字符串的,按照道理來說應該是全零啊,爲什麼會有個莫名其妙的0x40在這裏?
(全文完)
參考資料:
https://stackoverflow.com/questions/50762019/solidity-tx-destination-call-valuetx-valuetx-data
https://solidity.readthedocs.io/en/latest/control-structures.html#external-function-calls