You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: smart-contracts.asciidoc
+113
Original file line number
Diff line number
Diff line change
@@ -495,7 +495,120 @@ Additional error checking code like this will increase gas consumption slightly,
495
495
496
496
==== Calling other contracts (call, send, delegatecall, callcode)
497
497
498
+
Calling other contracts from within your contract is a very useful but potentially dangerous operation. We'll examine the various ways you can achieve this and evaluate the risks of each method.
498
499
500
+
===== Creating a new instance
501
+
502
+
The safest way to call another contract is if you create that other contract yourself. That way, you are certain of its interfaces and behavior. To do this, you can simply instantiate it, using the keyword +new+, as with any object-oriented language. In Solidity, the keyword +new+ will create the contract on the blockchain and return an object that you can use to reference it. Let's say you want to create and call a +Faucet+ contract, from within another contract called +Token+:
503
+
504
+
505
+
----
506
+
contract Token is mortal {
507
+
Faucet _faucet;
508
+
509
+
constructor() {
510
+
_faucet = new Faucet();
511
+
}
512
+
}
513
+
----
514
+
515
+
This mechanism for contract construction ensures that you know the exact type of contract and its interface. The contract +Faucet+ must be defined within the scope of +Token+, which you can do with an +import+ statement, if the definition is in another file:
516
+
517
+
----
518
+
import "Faucet.sol"
519
+
520
+
contract Token is mortal {
521
+
Faucet _faucet;
522
+
523
+
constructor() {
524
+
_faucet = new Faucet();
525
+
}
526
+
}
527
+
----
528
+
529
+
The +new+ keyword can also accept optional parameters to specify the +value+ of ether transfer on creation, and arguments passed to the new contract's constructor, if any:
530
+
531
+
----
532
+
import "Faucet.sol"
533
+
534
+
contract Token is mortal {
535
+
Faucet _faucet;
536
+
537
+
constructor() {
538
+
_faucet = (new Faucet).value(0.5 ether)();
539
+
}
540
+
}
541
+
----
542
+
543
+
If we endow the created +Faucet+ with some ether, we can also then call the +Faucet+ functions, which operate just like a method call. In this example, we call the +destroy+ function of +Faucet+, from within the +destroy+ function of +Token+:
544
+
545
+
----
546
+
import "Faucet.sol"
547
+
548
+
contract Token is mortal {
549
+
Faucet _faucet;
550
+
551
+
constructor() {
552
+
_faucet = (new Faucet).value(0.5 ether)();
553
+
}
554
+
555
+
function destroy() ownerOnly {
556
+
_faucet.destroy();
557
+
}
558
+
}
559
+
----
560
+
561
+
===== Addressing an existing instance
562
+
563
+
Another way we can use to call a contract, is to cast the address of an existing instance of the contract. With this method, we apply a known interface to an existing instance. It is therefore critically important that we know, for sure, that the instance we are addressing is in fact of the same type as we assume. Let's look at an example:
564
+
565
+
----
566
+
import "Faucet.sol"
567
+
568
+
contract Token is mortal {
569
+
570
+
Faucet _faucet;
571
+
572
+
constructor(address _f) {
573
+
_faucet = Faucet(_f);
574
+
_faucet.withdraw(0.1 ether)
575
+
}
576
+
}
577
+
----
578
+
579
+
Here, we take an address provided as an argument to the contructor and we cast it as a +Faucet+ object. This is much riskier than the previous mechanism, because we don't in fact know whether that address is in fact a +Faucet+ object. When we call +withdraw+, we are assuming that it accepts the same arguments and executes the same code as our +Faucet+ declaration, but we can't be sure. For all we know, the +withdraw+ function at this address could execute something completely different from what we expect, even if it is named the same. Using addresses passed as input and casting them into specific objects is therefore much more dangerous than creating the contract ourselves.
580
+
581
+
===== Raw call, delegatecall
582
+
583
+
Solidity offers some even more "low-level" functions for calling other contracts. These correspond diretly to EVM opcodes of the same name and allow us to construct a contract-to-contract call manually. As such, they represent the most flexible *and* the most dangerous mechanisms for calling other contracts.
584
+
585
+
Here's the same example, using a +call+ method:
586
+
587
+
----
588
+
contract Token is mortal {
589
+
constructor(address _f) {
590
+
_f.call("withdraw", 0.1 ether);
591
+
}
592
+
}
593
+
----
594
+
595
+
As you can see, this type of +call+, is a _blind_ call into a function, very much like constructing a raw transaction, only from within a contract's context. It can expose our contract to a number of security risks, most importantly _reentrancy_, which we will talk about in more detail in <<reentrancy>>. The +call+ function will return false if there is a problem, so we can evaluate the return value, for error handling:
596
+
597
+
----
598
+
contract Token is mortal {
599
+
constructor(address _f) {
600
+
if !(_f.call("withdraw", 0.1 ether)) {
601
+
revert("Withdrawal from faucet failed");
602
+
}
603
+
}
604
+
}
605
+
----
606
+
607
+
Another variant of +call+ is +delegatecall+, previously +callcode+. The +callcode+ method will be deprecated soon, so it should not be used.
0 commit comments