(ADS) Address Delegate Service

ADS stores a list of Routes that are used to supply the correct and current address for a DApp’s smart contract. ADS can be accessed a few different ways, within the ethereum network via solidity, from a browser via javascript, and from the Atra Console via supported browser.

Note

ADS is currently in a pre-beta stage, please visit our site to sign up for early access. https://atra.io/ads

Intro

ADS is solving a problem for developers that might not be apparent when first starting to develop smart contracts. The problem reveals itself when you’re ready to update a contract or its many dependencies. If you haven’t figured this out by now, you cannot update a contracts code once it’s live. What you can do is modify storage inside the contract that was predetermined by your code at the time of deployment. If you were to make code changes and deploy a new (updated) version of the contract, you’d have a different address than the previous contract, this would leave all existing dependencies stuck on the old address until they were able to update their code.

To solve this problem DApps need to maintain a mechanism that keeps track of what the real path is to a products contract. This is a lot of work and starts to become its own project rather than the idea you originally started to build. Don’t fear, that’s why ADS is here.

How does ADS work?

ADS is a service and smart contract that developers can leverage when there is a need to interact with a contract, whether that is contract they own or a dependency of their contract. Instead of blindly applying what you think is the correct and current address to an interface, make a call to ADS with the name of the Route that maintains the product smart contract address you need, ADS will return the correct and current address.

Using ADS as a pattern to access a contract will allow the owner of the ADS Route to update the Route’s address and all clients using ADS to access the product will seamlessly be updated to the new address without ever having to change any code on their end. Owners will be able to hot swap contracts without breaking links to any dependencies.

Walkthrough

Note

We will not be covering smart contract basics in this walkthrough. Head over to the Solidity Documentation if you need an intro to smart contracts.

In this Walkthrough we will learn how to structure our smart contracts to create a small network for ADS to control the routing.

Our example network will consist of a storage contract, two logic contracts, and the ADS contract. ADS will act as a middle man between the storage contract and the logic contracts, serving the storage contract the correct and current logic contract address before every request.

The two logic contracts will be using the same solidity interface, allowing the storage contract to apply the same interface to either of the logic contracts without code changes. The logic contracts are essentially interchangeable within the storage contract. The storage contract will be using ADS to supply the address for it’s logic. If we switch the logic Route in the ADS to serve a different logic address, the storage contract will get that update without any code changes.

1. Setting Up

Note

Metamask is a prerequisite to this Walkthrough. Need help setting up a metamask account? Follow our quick Getting Started with Atra guide and come back when you’re done.

Let’s set our ethereum network and load all of the resources in new tabs we will need complete the walkthrough.

  1. Set Metamask to use the Rinkeby Test Net. Reference our Getting Started with Atra for help.
  2. Open Remix Ethereum IDE in a new tab (https://remix.ethereum.org/)
  3. Open the Atra ADS Console in a new tab (https://atra.io/console?service=ads)

All contracts used in the walkthrough can be found on the Atra Github.

Note

All contracts used in the walkthrough are purely for example purposes only.

Before we proceed to the next section make sure you’ve completed list above and the Getting Started with Atra guide.

2. Logic Contracts

Let’s take a look at what we refer to as a logic contract. Below we’ve listed the two contracts we’re going to use in our network. First thing you notice is that both contracts are very similiar. They are pretty identical except for the name of the contract and how they calculate inputs num1 and num2 in the Calc function. The calc function is essentially the logic of the contract. Logic can range from a simple addition to complex algorithms that connect to many other contracts. The point is that it doesn’t matter so much of what the functions are doing but more that they share a common interface or set of functions, and in this case that’s the Calc function.

pragma solidity^0.4.23;

interface Logic {
    function Calc(uint num1, uint num2) external view returns(uint result);
}

contract ExampleLogic_Add is Logic{
    function Calc(uint num1, uint num2) external view returns(uint result) {
        return num1 + num2;
    }
}

The above contract ExampleLogic_Add uses the addition operator to combine the two inputs.

pragma solidity^0.4.23;

interface Logic {
    function Calc(uint num1, uint num2) external view returns(uint result);
}

contract ExampleLogic_Mult is Logic{
    function Calc(uint num1, uint num2) external view returns(uint result) {
        return num1 * num2;
    }
}

The above contract ExampleLogic_Mult uses the multiplication operator to combine the two inputs.

3. Deploying a Contract with Remix

We are going to open our IDE Remix and paste each of the above logic contracts code into a new file. Name the files ‘ExampleLogic_Add.sol’ and ‘ExampleLogic_Mult.sol’. Once the files are created and compiled by Remix, use the tabs in the upper right, select the run tab and set the ‘Environment’ dropdown to ‘Injected Web3’. This is telling remix you want to use your metamask wallet and the network that it’s currently set to, in this instance it’s going to be Rinkeby Test Net.

Just down the page a little you can click the ‘Deploy’ button, this will initiate a transaction with metamask that need to be approved by you, it’s for the cost of deploying your contract to the test net. Click ‘Submit’ and the transaction will be sent containing the creation of a contract. You must wait until the transaction is approved, once it’s finished after a minute or two at the most, you will see the contracts endpointd returned with it’s name and address as the title, below the deploy button. From here you can interact with your contract directly by clicking on the buttons that represent a function or public variable in the deployed live contract.

Repeat this processes for both logic contracts we’ve listed above. This step is complete when have two contracts deployed to the Rinkeby Test Net in two different files. Clicking the Calc button with two numbers as input has the expected result.

Now that you have you’re contracts deployed and working correctly, what we need to do now is grab the address of the first logic contract ‘ExampleLogic_Add’ from remix. The address is located at the top the contracts properties list of buttons and inputs to the right of the name, there is a copy address button. Once you have the address we can head into the next section where we are going to interact with ADS using the Atra Console.

4. Creating a Route with ADS Console

First thing we need to do is open the ADS Console (https://atra.io/console?service=ads). The console is a tool used for easily interacting with the ADS smart contract. The console makes sure your Metamask account is in the right state before allowing you to interact. You should see a banner that says ‘Connection Successful’, if you see a different message follow the on screen directions to resolve it.

Lets jump right in and click Create Route. A dialog will popup with a create route form, take a second and hover over the tooltips. Our route name can be anything upto 100 characters and unique within ADS. Go ahead and name your route, an example route name for your logic contract may be ‘[your name].ExampleLogic’, if that’s not available just play around with different names until you’ve found an available one. The next field is the contracts address, here is where you’ll insert the copied address form the first logic contract from the previous section. If you no longer have the address just repeat the ‘Deploying a Contract with Remix’ section again.

The next field, ABI is not going to be used in this example so we can just put ‘pending’ in that input.

Click ‘Create Route’, you’ll notice a Metamask popup dialog will appear asking to confirm the transaction, click submit. If you do not see the metamask popup check the extension itself by clicking on it in your browser toolbar.

Once the transaction has been submitted you’ll see a new record added to the ‘Transaction History’ in the console. The status will start as pending and soon change to complete once the transaction has been mined.

Note

You do not need to refresh the page while waiting for the transaction to complete. Once you see a completed status, use the ‘Refresh’ button to the left of the transaction history.

When our transaction has been completed, click the ‘Refresh’ button. You will now see your newly created route in the table and it’s details below the table.

What we just did was create a route record within the ADS contract using the ADS Console and our Metamask Wallet for the transaction fee. The Address of our Metmask Wallet is now the owner of the route. Owners have the ability to Schedule Updates and Transfer Route Ownership.

To recap, we now have two logic contracts deployed and live on the Rinkeby Test Net. We have created a new route with the ADS Console and set the Route Address to the address to the logic contract ‘ExampleLogic_Add’.

5. Storage Contract

Now that we have a way to calculate two numbers in our logic contract, lets connect that logic to what we’ve been referring to as a storage contract. Below we the code for our storage contract

pragma solidity^0.4.23;

interface Logic {
    function Calc(uint num1, uint num2) external view returns(uint result);
}

interface ADS {
    function GetAddress(uint routeId, string name) external view returns(address currentAddress);
}

contract ExampleStorage {

    address public AdsAddress;
    uint public num1 = 500;
    uint public num2 = 300;

    constructor() public {
        AdsAddress = 0x472510ff257d04ac8ced332bfc5719ab30871202;
    }

    function Calculate() public view returns(uint result) {
        ADS ads = ADS(AdsAddress);
        Logic logic = Logic(ads.GetAddress(0, "Replace with Route Name"));
        return logic.Calc(num1, num2);
    }
}

This contract has a little more going on than the logic contracts, so lets break down what’s actually happening here.

Let’s look at the first interface

interface Logic {
    function Calc(uint num1, uint num2) external view returns(uint result);
}

This should look familiar because we are using the same interface in our logic contracts.

The next interface is the ADS interface.

interface ADS {
    function GetAddress(uint routeId, string name) external view returns(address currentAddress);
}

This gives our code the ability to call functions in the ADS smart contract when applying the address to the interface. If you view the actual ADS smart contract interface you’ll notice it contains a few more functions than this one. The reason for the lack of function is because we are only needing to use the one function ‘GetCurrentAddress’.

Now that we’ve looked over the Storage contract dependency interfaces lets see how they are used within the core of the Storage contract.

contract ExampleStorage {

    address public AdsAddress;
    uint public num1 = 500;
    uint public num2 = 300;

    constructor() public {
        AdsAddress = 0x472510ff257d04ac8ced332bfc5719ab30871202;
    }

    function Calculate() public view returns(uint result) {
        ADS ads = ADS(AdsAddress);
        Logic logic = Logic(ads.GetAddress(0, "Replace with Route Name"));
        return logic.Calc(num1, num2);
    }
}

The first thing we notice are variables being declared, this is essentially the storage of the contract. The storage is basically whatever information you need to store in the smart contract for the foreseeable future. Next we have the first function on the contract, this is the constructor, it’s ran only once, at the creation on the contract when it’s being deployed. In our constructor we are setting the value of AdsAddress. This is the smart contract address to the ADS contract Atra has deployed on the Rinkeby Test Net.

Finally the function that brings the whole network together, Calculate.

function Calculate() public view returns(uint result) {
    ADS ads = ADS(AdsAddress);
    Logic logic = Logic(ads.GetAddress(0, "Replace with Route Name"));
    return logic.Calc(num1, num2);
}

In the Calculate function we set it to public, allowing anyone in the world to call the function. We set the function to ‘view’ telling ethereum we are only going to look at the storage and not change it. We then say we are returning a number named result, which will be the result of our logic contract after it has crunched the two numbers and returned a result.

Inside the Calculate function we see the interfaces we defined above the contract now being used. When you apply an address to an interface you can interact with the smart contract at that address only if it can fulfill the interface. In our case the first interface we use is the ADS interface. We create an instance of the ADS contract to use. We then do the same thing with the Logic interface, expect this is where we use the ADS instance. We use the instance of the ADS contract to get the address of the route we created earlier, which will point to the ExampleLogic_Add contract. When it’s all put together we get an instance of a logic contract based on the address the route returns. Next we use the instance of the Logic contract to call the function Calc, and supply the storage variables (num1, num2) as input for the Calc function. Then we return the result of the Calc function as the result of the Calculate function.

Now lets copy the full storage contract from above. Open remix paste the code into a new file named ‘ExampleStorage.sol’. Before you click the deploy button, lets make one change to the code. Where it says ‘Route Name’ in the contract code, replace that with the name of the route you created earlier in the ADS Console. Above the ‘Deploy’ button there is a dropdown with a list of interfaces and contracts that your file has. By default the ADS interface will be selected, we need to change that to the name of the contract, ‘ExampleStorage’. Click ‘Deploy’, wait for the transaction to complete in remix, once it’s done remix displays the contract name and address with it’s public functions in the right sidebar under the deploy button. Lets click the button ‘Calculate’, this is going to call the function Calculate in the live contract, which is going to create an instance of the ADS contract and also an instance of the Logic contract based on what our Route returns for the address. Next it’s going to use the logic by calling Calc on that Logic instance, and returning the result. The expected result when using the address returned by the Route that points to the live ‘ExampleLogic_Add’ contract, is going to be 800. Leave the remix window open so you can come back to the contract and click the Calculate button after you’ve updated the route in the next section.

We now have a fully functioning network of contracts ready to be updated with new logic.

6. Scheduling an Update

There are a couple reason you may want to update logic that a storage contract uses. Let’s pretend you’ve created an ethereum trading card game and after you’ve deployed the storage contract you notice there’s a bug in the logic for calculating the correct price a customer needs to pay for a card. You can correct the bug in the logic contract, deploy it, then schedule an update for the route to use the new contracts address. Seamlessly fixing the issue without any interruption to the DApp. This is just one of many scenarios where ADS really shines. Let’s look at how we schedule an update.

Scheduling an Update to your Route is simple, open up the ADS Console just like before when we created a route. If you have created multiple routes that’s okay, just select the logic route we created earlier for this example, if you’re still not sure it’s the route name we used in our storage contract above. After selecting a route from the routes table you enable the actions dropdown above the table. The actions dropdown applies only to the selected route. In the Actions dropdown select ‘Schedule Update’. A popup dialog should appear with a title of ‘Schedule Update’, the route name should again be the name of the route we used in our storage contract. The next field is the ‘Release Date’, this field has two options, you can either choose to release the update right now, meaning you’ll replace the current ExampleLogic_Add address without a wait time, or you can select a date in the future to plan the update. For this example we are going to leave the default of releasing the update now.

The next field in the form is Address, this is the address to the new logic contract, in our case we are going to use the second logic contract we created earlier in the walkthrough, ExampleLogic_Mult. If you can’t find the address to the contract just go repeat the Deploying a Contract with Remix section and only deploy the ExampleLogic_Mult contract. Once you have the contract created, grab the address once again and paste it into the address field in the popup dialog and set the Abi field to ‘pending’, and click ‘Save’.

After you approve the transaction via metamkas popup, or by clicking on the extension itself if it doesn’t popup, wait for the transaction history to show complete for the update. Click refresh and you’ll see that the address, version, and release date for the route have changed to the new Logic contract.

Lets head back over to remix and click the Calculate button for your storage contract. You should see the result 150,000.

7. Conclusion

We have now successfully created a network of contracts using ADS to control routing, enabling us to be able to update contracts while they are live.

Solidity Contract Reference

ABI

To create a JavaScript instance of the ADS contract with web3.js you’ll need the offical ABI for ADS. The offical ABI is hosted on our github.

Interface

To create a solidity instance of the ADS contract you’ll need to use the official ADS interface for solidity that’s listed below.

interface IADS {
    function Create(string name, address currentAddress, string currentAbiLocation) public payable returns(uint newRouteId);

    function ScheduleUpdate(uint _id, string _name, uint _release, address _addr, string _abiUrl) public returns(bool success);

    function Get(uint _id, string _name) public view returns(string name, address addr, string abiUrl, uint released, uint version, uint update, address updateAddr, string updateAbiUrl, uint active, address owner, uint created);

    function GetRouteIdsForOwner(address _owner) public view returns(uint[] routeIds);

    function GetAddress(uint _id, string _name) public view returns(address addr);

    function GetAddressAndAbi(uint _id, string _name) public view returns(address addr, string abiUrl);

    function RoutesLength() public view returns(uint length);

    function NameTaken(string _name) public view returns(bool taken);

    function TransferRouteOwnership(uint _id, string _name, address _owner) public returns(bool success);

    function AcceptRouteOwnership(uint _id, string _name) public returns(bool success);
}

Create

Create(_name, _addr, _abiUrl) public payable returns(id);

Create(_name, _addr, _abiUrl) function is used to create and append a new route object to ADS contract routes list. After creating the route it is immediately available and will return the initiated parameters as the active route info until the owner calls the ScheduleUpdate function.

Parameters

  1. _name - string: The immutable name of the route that’s being created. A name must be unique within the ADS contract and has a max length of 100 characters. Use the NameTaken function to ensure the name is available before submitting the transaction.
  2. _addr - address: The starting contract address for the route.
  3. _abiUrl - string: The starting contract ABI url for the route. Max 256 characters.

Returns

  1. id - uint: The index of the Route object within the Routes list.

Event

event RouteCreated(string name, address owner);

ScheduleUpdate

ScheduleUpdate(_id, _name, _release, _addr, _abiUrl) public returns(success);

ScheduleUpdate(_id, _name, _release, _addr, _abiUrl) function is used when the owner of the route wants to schedule an update for their route. Scheduling an update will overwrite an update that has a release date still in the future. When the release date is in the past, the ADS contract will serve the update position as active.

Parameters

  1. _id - uint: The index of the Route object within the Routes list. When _name length is 0 the function uses _id to look up the Route.
  2. _name - string: A unique string within the ADS contract used to lookup a Route. When _name is given the _id is ignored and the Route is looked up by _name.
  3. _release - uint: An epoch timestamp in seconds of when the update will be active.
  4. _addr - address: The contract address for the update position of the route.
  5. _abiUrl - string: The url to the contract ABI for the update position of the route.

Returns

  1. success - bool: Returns true is the function executed successfully.

Event

event UpdateScheduled(string name, address owner);

Get

Note

This function is not yet supported by contract to contract calls. To access this function use web3.js. To access a Route’s address in solidity use the GetAddress() function.

Get(_id, _name) public view returns(
    name,
    addr,
    abiUrl,
    released,
    version,
    update,
    updateAddr,
    updateAbiUrl,
    active,
    owner,
    created
);

Get(_id, _name) is used when a client needs to access a Route’s full list of properties. This function is heavy relied on by the Atra Console to display the state of an owners routes.

Parameters

  1. _id - uint: The index of the Route object within the Routes list. When _name length is 0 the function uses _id to look up the Route.
  2. _name - string: A unique string within the ADS contract used to lookup a Route. When _name is given the _id is ignored and the Route is looked up by _name.

Returns

  1. name - string: A unique name set at creation by the owner.
  2. addr - address: The contract address for the current position of the route. Use the active property to determine what position to use.
  3. abiUrl - string: The url to the contract ABI for the current position of the route. Use the active property to determine what position to use.
  4. released - uint: An epoch timestamp in seconds of when the active route address and ABI last changed. This is dynamic depending what address info is being served as active.
  5. version - uint: A number that auto increments everytime a new route address and ABI is served as active.
  6. update - unit: An epoch timestamp in seconds for when updateAddr and UpdateAbiUrl will be served as active.
  7. updateAddr - address: The contract address for the update position of the route. Use the active property to determine what position to use.
  8. updateAbiUrl - string: The url to the contract ABI for the update position of the route. Use the active property to determine what position to use.
  9. active - uint: Returns either 0 (current) or 1 (update) representing which position to use as the correct and current route address and ABI. 0 = (addr, abiUrl) 1 = (updateAddr, updateAbiUrl)
  10. owner - address: The wallet address of the owner of the route.
  11. created - uint: An epoch timestamp in seconds of when the route was created.

GetRouteIdsForOwner

GetRouteIdsForOwner(_owner) public view returns(ids);

GetRouteIdsForOwner(_owner) is used to get a list of route ids that are owned by a wallet address. This function can be used to in conjuction with any ADS function that accepts _id as an input parameter. The Atra Console uses this function to build a list of routes for the user.

Parameters

  1. _owner - address: The wallet address of the route owner.

Returns

  1. ids - uint[]: Array of route ids that are owned by _owner.

GetAddress

GetAddress(_id, _name) public view returns(addr);

GetAddress(_id, _name) is used by clients thay only need the active address for a route. This is a dynamic call that checks the update release date and returns either the current position or update position address.

Parameters

  1. _id - uint: The index of the Route object within the Routes list. When _name length is 0 the function uses _id to look up the Route.
  2. _name - string: A unique string within the ADS contract used to lookup a Route. When _name is given the _id is ignored and the Route is looked up by _name.

Returns

  1. addr - address: The active address for a the route.

GetAddressAndAbi

GetAddressAndAbi(_id, _name) public view returns(addr, abiUrl);

GetAddressAndAbi(_id, _name) function is used by clients that only need the active address and ABI of a route. If a client isn’t displaying route state, use this call over the Get function.

Parameters

  1. _id - uint: The index of the Route object within the Routes list. When _name length is 0 the function uses _id to look up the Route.
  2. _name - string: A unique string within the ADS contract used to lookup a Route. When _name is given the _id is ignored and the Route is looked up by _name.

Returns

  1. addr - address: The active address for a the route.
  2. abiUrl - string: The active ABI url for a the route.

RoutesLength

RoutesLength() public view returns(length);

RoutesLength() function is used when a client needs to know the total size of the routes list. Clients can use this number to display the last X amount of routes created.

Returns

  1. length - uint: The total number of routes in the routes list.

NameTaken

NameTaken(_name) public view returns(taken);

NameTaken(_name) function is used to determine wether or not a name has already been used within the ADS contract. The Atra Console uses this function in the create route form before allowing a user to submit the creation of a route.

Parameters

  1. _name - string: The route name.

Returns

  1. taken - bool: Returns true if the route name is taken. Returns false if the route name is available.

TransferRouteOwnership

TransferRouteOwnership(_id, _name, _owner) public returns(success);

TransferRouteOwnership(_id, _name, _owner) function is used by route owners that want to transfer the ownership of their route to another wallet address. Transfering a route is a two part process and must use the AcceptRouteOwnership to finalize the transfer.

Parameters

  1. _id - uint: The index of the Route object within the Routes list. When _name length is 0 the function uses _id to look up the Route.
  2. _name - string: A unique string within the ADS contract used to lookup a Route. When _name is given the _id is ignored and the Route is looked up by _name.
  3. _owner - address: The target wallet address of the new owner for the route. _owner must call AcceptRouteOwnership to finalize the transfer and take ownership of the route.

Returns

  1. success - bool: Returns true if the function was successfull.

AcceptRouteOwnership

AcceptRouteOwnership(_id, _name) public returns(success);

AcceptRouteOwnership(_id, _name) function is used to finalize a transfer of a route to a new address. Caller must be the tragert wallet address of the TransferRouteOwnership function.

Parameters

  1. _id - uint: The index of the Route object within the Routes list. When _name length is 0 the function uses _id to look up the Route.
  2. _name - string: A unique string within the ADS contract used to lookup a Route. When _name is given the _id is ignored and the Route is looked up by _name.

Returns

  1. success - bool: Returns true if the function was successfull.