can a transaction's receipt have a '1' in its status field but still have had one of its internal calls fail?
Yes.
I tend to agree with James:
In most reasonable cases, the only sane action is for smart contact A to abort.
But, the alternative exists.
If the Outer contract uses .send() it continues after inner fails. The compiler raises a warning if the result isn't checked, but carrying on is possible. This sort of thing almost always adds undesirable complexity to smart contracts, so don't do it unless you really know what you're doing.
function soldierOn() public {
bool success = target.send(amount);
if(success) {
// do something
} else {
// do something else.
// Outer is still executing and can succeed (by its definition of success).
}
call() returns (bool success, bytes memory response). It's possible to carry on. This requires a departure from Solidity's more natural syntax and default behaviour.
pragma solidity 0.5.11;
contract Outer {
Inner inner;
constructor(Inner _inner) public {
inner = _inner;
}
/**
* Pass in false to make Inner to fail.
*/
function explored(bool succeed) public returns(bool success, bytes memory response) {
// instead of inner.tryIt(succeed) that would make everything fail if Inner fails ...
(success, response) = address(inner).call(abi.encodeWithSignature("tryIt(bool)", succeed));
}
}
contract Inner {
event LogActivity(address sender, bool successful);
function tryIt(bool succeed) public returns(bool result) {
emit LogActivity(msg.sender, succeed);
require(succeed, "You wanted me to fail.");
return true;
}
}
Again, just to reinforce the point for others who might find this. There are other reasons for using call(), e.g. managing gas or value transfer, and the right thing to do is almost always to just abort if the Inner contract complains.
(success, response) = address(inner).call(abi.encodeWithSignature("tryIt(bool)", succeed));
require(success, "Inner failed, so we are aborting.");
Hope it helps.