4

Here's the page containing the example https://solidity.readthedocs.io

Here are two contracts that are supposed to work together(my questions are below):

enter image description here

Here's how I think it works. Please correct my steps while I try to walk you through.

  1. Since one contract is per one address, these two contracts are deployd to two different adresses.

  2. 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)

  3. 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.

  4. Now I'm stumped. How does new OwnedToken(name) works? How does TokenCreator contract sees OwnedToken?

In addition I just discoverd a compiler error:

enter image description here

It's so exciting to plough trough all this stuff! Thank you, you're great!


EDIT: Made some changes, and it now makes more sense enter image description here

1 Answer 1

5

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"
8
  • The powerful @BokkyPooBah! A billion thanks to you for your time and efforts! It's all clear now, except one little thing...
    – manidos
    Jul 27, 2016 at 13:28
  • 1
    On line 12... Is msg.sender the address of the TokenCreator contract or the address of the user who called TokenCreator.createToken(...)? If the former is true, then line 31 will always return and nobody could transfer funds except TokenCreator contract. I'm really sorry for being such a dummy!
    – manidos
    Jul 27, 2016 at 13:56
  • On line 12, msg.sender is the account of the user who called TokenCreator.createToken(...). This account is the only authorised account to call changeName(...) and transfer(...). Once transferred, the OwnedToken is owned by the account that the original owner transferred to. Don't apologise - I've spent some time on Solidity and the Ethereum blockchain and am still learning more everyday. This example is a little bit complicated, especially since you cannot easily get the return value from TokenCreator.createToken(...). Jul 27, 2016 at 15:15
  • 1
    I swear to God, you're the man!!! You are what makes Ethereum great! All of it makes sense, but ... Then there's no way to transfer the token. You can't call tokenCreator.transfer() because there's no such function in tokenCreator contract. However, you can call OwnedToken.transfer() directly from you account address, but the if statement would throw an error. I've added some modifications to the code in my question and the contract seems now more logical. Please check it out!
    – manidos
    Jul 28, 2016 at 10:02
  • 1
    paging you in case you missed new code. I've added it later than I posted the previous message here...
    – manidos
    Jul 28, 2016 at 10:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.