Skip to content

Lists examples of all the ways two smart contracts written in Solidity can interact.

Notifications You must be signed in to change notification settings

Genysys/SmartContractInteractions

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Smart Contract Interactions

This folder lists examples of all the ways two smart contracts written in Solidity can interact, as well as some common problems solidity programmers come across.

Different Smart Contract Invocations

Given a smart contract sp, there are six different ways sp can invoke and interact with another smart contract sq. I will describe these six different invocation categories below. Additionally, there are coded examples of these invocation categories in the CallingContract.sol file.

Note that in the following I use <ContractType>, <ContractName>, <FunctionName> and <ContractAddress> as placeholders.

  • Direct Invocation: new <ContractType> or <ContractName>.<FunctionName> - This invocation method is the most simple way for smart contract sp to interact with a function fx in smart contract sq. If fx is contract sq's constructor, then it is called through the new keyword followed by a reference to the <ContractType> of sq. Otherwise if fx is not a constructor, sp must have already instantiated a <ContractName> variable representing sq through prior knowledge of the <ContractAddress> of sq.

  • Low Level Call Invocation: <ContractAddress>.call(...) - This invocation method allows smart contract sp to invoke any function of smart contract sq. It is more complex than a direct invocation as the programmer needs to enter the function name and parameters types via string input and so no complie time checks can be made on if they are correct. If the programmer does not enter a valid function name and/or parameter types, then the fallback function of sq is called (if it exists).

  • Delegation: <ContractAddress>.delegatecall(...) or <ContractAddress>.callcode(...) - This invocation method allows smart contract sp to invoke any function of smart contract sq but for the function to operate over sp's storage. Therefore delegatecalls are a security risk and require sp to trust sq. Note that delegatecall can be seen as a bug fix of callcode as callcode does not preserve msg.sender when a smart contract sp delegates to another smart contract sq. That is, if callcode was used sq would have sp as msg.sender, but if delegatecall was used sq would still have access to the msg.sender of sp.

  • Send: <ContractAddress>.send(...) - This invocation method allows smart contract sp to send ether to recipient smart contract sq as long as sq has a fallback function implemented.

  • Transfer: <ContractAddress>.transfer(...) - This invocation method allows smart contract sp to transfer ether to recipient smart contract sq as long as sq has a fallback function implemented. Transfer is logically the same as the send invocation apart from transfer reverts on failure.

  • Self Destruct: selfdestruct(<ContractAddress>) or suicide(<ContractAddress>) - This invocation method allows smart contract sp to forceably send ether to the recipient smart contract sq even if no fallback is implemented in sq. Note that suicide is a depreciated alias of selfdestruct.

See CallingContract.sol for examples of all of these invocation methods.

Smart Contract Invocation Comparison

I will now discuss the comparison between the invocation categories according to different criteria, where the discussion is summarised in the following table:

Invocation comparison

  • Can Set Gas Limit - Which invocation methods allow an explicit gas limit (for the called contract) to be set by the programmer? This is an optional feature for the direct, low level call and delegation invocations. The send and transfer invocations have a preset gas limit. Whereas no additional gas is needed for the self destruct invocation - instead users are "rewarded" for self-destructing their contracts with a negative gas cost for using this invocation. A reward is given because after a self destruct invocation is used, the code and storage of the destructed contract are removed from the ethereum virtual machine going forward. Note that nodes with access to the block where the contract was originally deployed will still be able to recover the contract's associated bytecode, so the concept of destruction is different from complete removal.

  • Gas Limit - There are currently explicit gas limits for send and transfer, set at 2300 each. While the direct, low level call and delegation invocations can accept all remaining gas from the calling function or an explicit amount. Whereas self destruct uses negative gas for reasons described previously. Note that all of the direct, low level call and delegation invocations could allow reentrancy into the original contract's functions and/or storage, either by design (delegation) or as a by-product of allowing flexible gas limits (direct and low level call).

  • Return value if error occurs - After each invocation, what happens if there is an error? For direct and transfer invocations, they will both throw an error. Whereas the low level call, delegation and send invocation will only return false, hence if you want to propagate errors from these invocations, they should be wrapped in a require function. Finally, the self destruct invocation does not return a value or throw an error - as there is no contract left to return or throw anything.

  • Storage used - After the invocation, whose storage can be changed in the called contract's function? The direct and low level call invocations operate in the more natural way where the called contract can only change it's own storage. The delegation invocation is more complicated as it allows the called contract's functions to change the storage of the original contract. The send and transfer invocations do not provide enough gas to allow the called contract to make any state changes. Whereas the self-destruct invocation removes the storage of the contract performing the invocation.

  • Ether sent to called contract - The send and transfer invocations require an explicit ether amount to be given. Whereas the direct and low level call invocations allow ether to be optionally sent. The delegation invocation does not allow ether to be passed on, as giving ether via the delegation invocation is effectively giving ether to yourself due to the way contract storage is manipulated in this invocation. Finally, self destruct forces the contract to pass on all remaining ether to a given address.

Common Solidity Programming Issues

Programmers unfamiliar with solidity can be caught out by the following two issues relating to the flow of code execution:

  • Possibly of Reentrancy: When a smart contract sp's function fx calls another function fy, the rest of function fx is effectively paused until fy completes. This can be an issue if fx wants to make some state changes after fy completes and fy resides in a different smart contract sq (which could have been created by a malicious individual). In this case fy can attempt to exploit the fact that fx is on pause, by calling functions in sp to take advantage of the fact that fx has not made all of its state changes yet.

The withdraw() function in this solidity file contains an example of how a re-entry attack can occur. In this example, a user withdraws their shares, which is processed by the low level call invocation and the corresponding ether is sent to the user's address. If the user's address is a smart contract, this smart contract can re-call the withdraw() function to take more ether out of the contract than is placed in the user's account. This attack can occur because: (i) the user's account is only set to zero after the low level call invocation occurs; and (ii) this low level call invocation has forwarded all remaining gas to the called contract. To fix this issue, the programmer should have set the user's account to zero value before the ether was send to the user.

  • No Error Propagation: Some smart contract invocations propagate errors through the entire call stack while other invocations will instead return false if an error occurs. This can cause an issue if a programmer codes in a manner that expects all smart contract invocations to revert on failure.

The becomeKing() function in this solidity file contains an example of how not checking for error propagations can cause an application issue. In this example, a user bids ether to become the new king. If the user outbids the current king, then the user becomes the new king and the old king is paid some compensation. In this example a send invocation is used to give the old king compensation. The send invocation only allows for a small amount of gas to be used, meaning that if the old king is a smart contract that attempts to perform function calls after receiving ether, then this send invocation will fail. Crucially, there is no check in the becomeKing() function for if the send invocation fails. To fix this issue, another function could have been used for compensation requests from old kings where the compensation is made through the low level call invocation.

About

Lists examples of all the ways two smart contracts written in Solidity can interact.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published