Blockchain Technology: Solidity Tutorials -Part-VI.
Interfacing with other Contracts
There are two ways to interface with other contracts: Either call a method of a contract whose address is known or create a new contract. Both uses are shown in the example below. Note that (obviously) the source code of a contract to be created needs to be known, which means that it has to come before the contract that creates it (and cyclic dependencies are not possible since the bytecode of the new contract is actually contained in the bytecode of the creating contract).
contract OwnedToken {
// TokenCreator is a contract type that is defined below. It is fine to reference it
// as long as it is not used to create a new contract.
TokenCreator creator;
address owner;
bytes32 name;
function OwnedToken(bytes32 _name) {
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
nameReg.call("register", _name);
owner = msg.sender;
// We do an explicit type conversion from `address` to `TokenCreator` and assume that the type of
// the calling contract is TokenCreator, there is no real way to check.
creator = TokenCreator(msg.sender);
name = _name;
}
function changeName(bytes32 newName) {
// Only the creator can alter the name -- contracts are explicitly convertible to addresses.
if (msg.sender == address(creator)) name = newName;
}
function transfer(address newOwner) {
// Only the current owner can transfer the token.
if (msg.sender != owner) return;
// We also want to ask the creator if the transfer is fine.
// Note that this calls a function of the contract defined below.
// If the call fails (e.g. due to out-of-gas), the execution here stops
// immediately (the ability to catch this will be added later).
if (creator.isTokenTransferOK(owner, newOwner))
owner = newOwner;
}
}
contract TokenCreator {
function createToken(bytes32 name) returns (address tokenAddress) {
// Create a new Token contract and return its address.
return address(new OwnedToken(name));
}
function changeName(address tokenAddress, bytes32 name) {
// We need an explicit type conversion because contract types are not part of the ABI.
OwnedToken token = OwnedToken(tokenAddress);
token.changeName(name);
}
function isTokenTransferOK(address currentOwner, address newOwner) returns (bool ok) {
// Check some arbitrary condition.
address tokenAddress = msg.sender;
return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
}
}
Constructor Arguments
A Solidity contract expects constructor arguments after the end of the contract data itself. This means that you pass the arguments to a contract by putting them after the compiled bytes as returned by the compiler in the usual ABI format.
Contract Inheritance
Solidity supports multiple inheritance by copying code including polymorphism. Details are given in the following example.
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
// Use "is" to derive from another contract. Derived contracts can access all members
// including private functions and storage variables.
contract mortal is owned {
function kill() { if (msg.sender == owner) suicide(owner); }
}
// These are only provided to make the interface known to the compiler.
contract Config { function lookup(uint id) returns (address adr) {} }
contract NameReg { function register(bytes32 name) {} function unregister() {} }
// Multiple inheritance is possible. Note that "owned" is also a base class of
// "mortal", yet there is only a single instance of "owned" (as for virtual
// inheritance in C++).
contract named is owned, mortal {
function named(bytes32 name) {
address ConfigAddress = 0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970;
NameReg(Config(ConfigAddress).lookup(1)).register(name);
}
// Functions can be overridden, both local and message-based function calls take
// these overrides into account.
function kill() {
if (msg.sender == owner) {
address ConfigAddress = 0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970;
NameReg(Config(ConfigAddress).lookup(1)).unregister();
// It is still possible to call a specific overridden function.
mortal.kill();
}
}
}
// If a constructor takes an argument, it needs to be provided in the header.
contract PriceFeed is owned, mortal, named("GoldFeed") {
function updateInfo(uint newInfo) {
if (msg.sender == owner) info = newInfo;
}
function get() constant returns(uint r) { return info; }
uint info;
}
Note that above, we call
mortal.kill()
to "forward" the destruction request. The way this is done is problematic, as seen in the following example:contract mortal is owned {
function kill() { if (msg.sender == owner) suicide(owner); }
}
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ mortal.kill(); }
}
contract Base2 is mortal {
function kill() { /* do cleanup 2 */ mortal.kill(); }
}
contract Final is Base1, Base2 {
}
A call to
Final.kill()
will call Base2.kill
as the most derived override, but this function will bypass Base1.kill
, basically because it does not even know about Base1
. The way around this is to use super
:contract mortal is owned {
function kill() { if (msg.sender == owner) suicide(owner); }
}
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ super.kill(); }
}
contract Base2 is mortal {
function kill() { /* do cleanup 2 */ super.kill(); }
}
contract Final is Base1, Base2 {
}
If
Base1
calls a function of super
, it does not simply call this function on one of its base contracts, it rather calls this function on the next base contract in the final inheritance graph, so it will call Base2.kill()
. Note that the actual function that is called when using super is not known in the context of the class where it is used, although its type is known. This is similar for ordinary virtual method lookup.Multiple Inheritance and Linearization
Languages that allow multiple inheritance have to deal with several problems, one of them being the Diamond Problem. Solidity follows the path of Python and uses "C3 Linearization" to force a specific order in the DAG of base classes. This results in the desirable property of monotonicity but disallows some inheritance graphs. Especially, the order in which the base classes are given in the
is
directive is important. In the following code, Solidity will give the error "Linearization of inheritance graph impossible".contract X {}
contract A is X {}
contract C is A, X {}
The reason for this is that
C
requests X
to override A
(by specifying A, X
in this order), but A
itself requests to override X
, which is a contradiction that cannot be resolved.
A simple rule to remember is to specify the base classes in the order from "most base-like" to "most derived".
Abstract Contracts
Contract functions can lack an implementation as in the following example (note that the function declaration header is terminated by
;
).contract feline {
function utterance() returns (bytes32);
}
Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts:
contract Cat is feline {
function utterance() returns (bytes32) { return "miaow"; }
}
If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract.
Visibility Specifiers
Functions and storage variables can be specified as being
public
, internal
or private
, where the default for functions is public
and internal
for storage variables. In addition, functions can also be specified as external
.
External: External functions are part of the contract interface and they can be called from other contracts and via transactions. An external function
f
cannot be called internally (i.e. f()
does not work, but this.f()
works). Furthermore, all function parameters are immutable.
Public: Public functions are part of the contract interface and can be either called internally or via messages. For public storage variables, an automatic accessor function (see below) is generated.
Inherited: Those functions and storage variables can only be accessed internally.
Private: Private functions and storage variables are only visible for the contract they are defined in and not in derived contracts.
contract c {
function f(uint a) private returns (uint b) { return a + 1; }
function setData(uint a) inherited { data = a; }
uint public data;
}
Other contracts can call
c.data()
to retrieve the value of data in storage, but are not able to call f
. Contracts derived from c
can call setData
to alter the value of data
(but only in their own storage).Accessor Functions
The compiler automatically creates accessor functions for all public state variables. The contract given below will have a function called
data
that does not take any arguments and returns a uint, the value of the state variable data
. The initialization of state variables can be done at declaration.contract test {
uint public data = 42;
}
The next example is a bit more complex:
contract complex {
struct Data { uint a; bytes3 b; mapping(uint => uint) map; }
mapping(uint => mapping(bool => Data[])) public data;
}
It will generate a function of the following form:
function data(uint arg1, bool arg2, uint arg3) returns (uint a, bytes3 b)
{
a = data[arg1][arg2][arg3].a;
b = data[arg1][arg2][arg3].b;
}
Note that the mapping in the struct is omitted because there is no good way to provide the key for the mapping.
Fallback Functions
A contract can have exactly one unnamed function. This function cannot have arguments and is executed on a call to the contract if none of the other functions matches the given function identifier (or if no data was supplied at all).
contract Test {
function() { x = 1; }
uint x;
}
contract Caller {
function callTest(address testAddress) {
Test(testAddress).send(0);
// results in Test(testAddress).x becoming == 1.
}
}
Function Modifiers
Modifiers can be used to easily change the behaviour of functions, for example to automatically check a condition prior to executing the function. They are inheritable properties of contracts and may be overridden by derived contracts.
contract owned {
function owned() { owner = msg.sender; }
address owner;
// This contract only defines a modifier but does not use it - it will
// be used in derived contracts.
// The function body is inserted where the special symbol "_" in the
// definition of a modifier appears.
modifier onlyowner { if (msg.sender == owner) _ }
}
contract mortal is owned {
// This contract inherits the "onlyowner"-modifier from "owned" and
// applies it to the "kill"-function, which causes that calls to "kill"
// only have an effect if they are made by the stored owner.
function kill() onlyowner {
suicide(owner);
}
}
contract priced {
// Modifiers can receive arguments:
modifier costs(uint price) { if (msg.value >= price) _ }
}
contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;
function Register(uint initialPrice) { price = initialPrice; }
function register() costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint _price) onlyowner {
price = _price;
}
}
Multiple modifiers can be applied to a function by specifying them in a whitespace-separated list and will be evaluated in order. Explicit returns from a modifier or function body immediately leave the whole function, while control flow reaching the end of a function or modifier body continues after the "_" in the preceding modifier. Arbitrary expressions are allowed for modifier arguments and in this context, all symbols visible from the function are visible in the modifier. Symbols introduced in the modifier are not visible in the function (as they might change by overriding).
Post a Comment