Replace the code where you found the error with:
function changeName(bytes32 newName) {
// Only the creator can alter the name --
// the comparison is possible since contracts
// are implicitly convertible to addresses.
if (msg.sender == address(creator))
name = newName;
}
and it will compile in the Solidity Online Compiler and in the solc
compiler via geth
.
Q1 Since one contract is per one address, these two contracts are deployd to two different adresses.
You can compile and deploy both contracts separately - this would require some changes to the code. See the ClassicCheck contract
and the SafeConditionalHFTransfer
contracts that refers to ClassicCheck
by address the in How to conditionally send ethers to another account post-hard-fork to protect yourself from replay attacks.
But this example has been written for you to compile them together. You deploy TokenCreator
to a contract address.
When you call TokenCreator.createToken(...)
, it will create a new OwnedToken(...)
contract and return the contract address. You will have to store this address, as the TokenCreator
contract does not store it.
Q2 In the constructor function of OwnedToken contract creator variable is assigned a contract located at msg.sender address. Through this variable it's then possible to access functions of that contract. It leads me to think that the creator of this contract must be another contract(TokenCreator)
Yes.
Q3 I then look at the code of TokenCreator contract in order to find which function is responsible for creating OwnedToken contract. I find it on line 43.
And it is #50 that creates the OwnedToken
contract.
Q4 Now I'm stumped. How does new OwnedToken(name) works? How does TokenCreator contract sees OwnedToken?
new OwnedToken(name)
creates the new OwnedToken
contract. The createToken(...)
function then returns the address where OwnedToken(...)
has been deployed to.
Note
There is a problem with the example code as non-constant functions cannot easily return values. You will have to generate an Event to display the token creation address. See How to get values returned by non constant transaction functions? for more details.
Or you can modify the TokenCreator
contract to store the created contract address as shown in How to get return values when function with argument is called? where the result was stored and accessed by the answer.getNumResult("idOne")
constant function.
TokenCreator
does not store the return value of the createToken(...)
call, and therefore does not know where OwnedToken
has been deployed to.
When you call changeName(...)
, you will have to supply the address that OwnedToken
has been deployed to.
Let's Take A Modified TokenCreator
For A Test Run
Here's a modified TokenCreator
contract saved in TokenCreator.sol
:
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 public creator;
address public owner;
string public name;
// This is the constructor which registers the
// creator and the assigned name.
function OwnedToken(string _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 that.
creator = TokenCreator(msg.sender);
name = _name;
}
function changeName(string newName) {
// Only the creator can alter the name --
// the comparison is possible since contracts
// are implicitly 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.
if (creator.isTokenTransferOK(owner, newOwner))
owner = newOwner;
}
}
contract TokenCreator {
mapping(string => address) addresses;
function getAddress(string name) constant returns (address) {
return addresses[name];
}
function createToken(string name)
returns (OwnedToken tokenAddress)
{
// Create a new Token contract and return its address.
// From the JavaScript side, the return type is simply
// "address", as this is the closest type available in
// the ABI.
tokenAddress = new OwnedToken(name);
addresses[name] = tokenAddress;
}
function changeName(string oldName, string newName) {
// Again, the external type of "tokenAddress" is
// simply "address".
address tokenAddress = addresses[oldName];
delete addresses[oldName];
addresses[newName] = tokenAddress;
OwnedToken(tokenAddress).changeName(newName);
}
function isTokenTransferOK(
address currentOwner,
address newOwner
) returns (bool ok) {
// Check some arbitrary condition.
address tokenAddress = msg.sender;
return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
}
}
Using the stripCrLf
script in How to load Solidity source file into geth, I've flattened the source code to:
user@Kumquat:~$ echo "var tokenCreatorSource='`stripCrLf TokenCreator.sol`'"
var tokenCreatorSource='contract OwnedToken { TokenCreator public creator; address public owner; string public name; function OwnedToken(string _name) { owner = msg.sender; creator = TokenCreator(msg.sender); name = _name; } function changeName(string newName) { if (msg.sender == address(creator)) name = newName; } function transfer(address newOwner) { if (msg.sender != owner) return; if (creator.isTokenTransferOK(owner, newOwner)) owner = newOwner; }}contract TokenCreator { mapping(string => address) addresses; function getAddress(string name) constant returns (address) { return addresses[name]; } function createToken(string name) returns (OwnedToken tokenAddress) { tokenAddress = new OwnedToken(name); addresses[name] = tokenAddress; } function changeName(string oldName, string newName) { address tokenAddress = addresses[oldName]; delete addresses[oldName]; addresses[newName] = tokenAddress; OwnedToken(tokenAddress).changeName(newName); } function isTokenTransferOK( address currentOwner, address newOwner ) returns (bool ok) { address tokenAddress = msg.sender; return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff); }}'
I'm running a dev network using the following command (I have my password in passwordfile
):
geth --datadir ~/devdata --dev --mine --minerthreads 1 --unlock 0 --password ~/passwordfile console
I paste the flattened code in the geth
command line:
> var tokenCreatorSource='contract OwnedToken { TokenCreator public creator; address public owner; string public name; function OwnedToken(string _name) { owner = msg.sender; creator = TokenCreator(msg.sender); name = _name; } function changeName(string newName) { if (msg.sender == address(creator)) name = newName; } function transfer(address newOwner) { if (msg.sender != owner) return; if (creator.isTokenTransferOK(owner, newOwner)) owner = newOwner; }}contract TokenCreator { mapping(string => address) addresses; function getAddress(string name) constant returns (address) { return addresses[name]; } function createToken(string name) returns (OwnedToken tokenAddress) { tokenAddress = new OwnedToken(name); addresses[name] = tokenAddress; } function changeName(string oldName, string newName) { address tokenAddress = addresses[oldName]; delete addresses[oldName]; addresses[newName] = tokenAddress; OwnedToken(tokenAddress).changeName(newName); } function isTokenTransferOK( address currentOwner, address newOwner ) returns (bool ok) { address tokenAddress = msg.sender; return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff); }}'
And compile the code using the following command:
> var tokenCreatorCompiled = web3.eth.compile.solidity(tokenCreatorSource);
Version: 0.3.5-0/RelWithDebInfo-Linux/g++/Interpreter
path: /usr/bin/solc
undefined
You can see the ABI generated:
> tokenCreatorCompiled.TokenCreator.info.abiDefinition
[{
constant: false,
inputs: [{
name: "name",
type: "string"
}],
name: "createToken",
outputs: [{
name: "tokenAddress",
type: "address"
}],
type: "function"
}, {
constant: false,
inputs: [{
name: "oldName",
type: "string"
}, {
name: "newName",
type: "string"
}],
name: "changeName",
outputs: [],
type: "function"
}, {
constant: true,
inputs: [{
name: "name",
type: "string"
}],
name: "getAddress",
outputs: [{
name: "",
type: "address"
}],
type: "function"
}, {
constant: false,
inputs: [{
name: "currentOwner",
type: "address"
}, {
name: "newOwner",
type: "address"
}],
name: "isTokenTransferOK",
outputs: [{
name: "ok",
type: "bool"
}],
type: "function"
}]
> tokenCreatorCompiled.OwnedToken.info.abiDefinition
[{
constant: true,
inputs: [],
name: "creator",
outputs: [{
name: "",
type: "address"
}],
type: "function"
}, {
constant: true,
inputs: [],
name: "name",
outputs: [{
name: "",
type: "string"
}],
type: "function"
}, {
constant: false,
inputs: [{
name: "newOwner",
type: "address"
}],
name: "transfer",
outputs: [],
type: "function"
}, {
constant: false,
inputs: [{
name: "newName",
type: "string"
}],
name: "changeName",
outputs: [],
type: "function"
}, {
constant: true,
inputs: [],
name: "owner",
outputs: [{
name: "",
type: "address"
}],
type: "function"
}, {
inputs: [{
name: "_name",
type: "string"
}],
type: "constructor"
}]
Create the contract on the blockchain:
> var tokenCreatorContract = web3.eth.contract(tokenCreatorCompiled.TokenCreator.info.abiDefinition);
undefined
> var tokenCreator = tokenCreatorContract.new({
from:web3.eth.accounts[0],
data: tokenCreatorCompiled.TokenCreator.code, gas: 1000000},
function(e, contract) {
if (!e) {
if (!contract.address) {
console.log("Contract transaction send: TransactionHash: " +
contract.transactionHash + " waiting to be mined...");
} else {
console.log("Contract mined! Address: " + contract.address);
console.log(contract);
}
}
})
...
Contract mined! Address: 0xfdb30244a9d9b8e98e9dd57ac81728725830f54b
Create the BokkyPooBahToken
using the following command:
> tokenCreator.createToken("BokkyPooBahToken", {from:eth.accounts[0]})
"0xc55cc14b02bd0ca783dc7457bd5f09263fb5750135e7f7e4e2c070f6dd855785"
Let's view the address of the created token:
> var tokenAddress = tokenCreator.getAddress("BokkyPooBahToken")
> tokenAddress
"0xcd347e1c299b02bba605f74e06031e7d170a1aed"
Let's check out the created token details:
> var token = eth.contract(tokenCreatorCompiled.OwnedToken.info.abiDefinition).at(tokenAddress);
undefined
> token.owner()
"0xfdb30244a9d9b8e98e9dd57ac81728725830f54b"
// Above is the TokenCreator contract address
> token.name()
"BokkyPooBahToken"
Let's change the name of the token:
> tokenCreator.changeName("BokkyPooBahToken", "BoppyKooBahToken", {from: eth.accounts[0]})
"0xa68045d36f313bad2abed292e86729ac9cf94c12a368b4d73a78f4a2826edf32"
> token.name()
"BoppyKooBahToken"