Affiliate Program

## Affiliate Program * Great case study for OpenRelay * Smart Contracts * Web3 dApp * Microservices Note: * Breadth of categories * All fairly simple components * Cover implementation without losing sight of big picture
## Affiliate Program ### Architectural Overview
## Affiliate Contract * Written in Solidity

Some Boilerplate


							pragma solidity ^0.4.18;

							import 'zeppelin-solidity/contracts/math/SafeMath.sol';
							import 'zeppelin-solidity/contracts/token/ERC20/ERC20.sol';
							import './WETH9.sol';

							interface Registry {
							    function isAffiliated(address _affiliate) external returns (bool);
							}
						

Contract Data



							contract Affiliate {
							  struct Share {
							      address shareholder;
							      uint stake;
							  }

							  Share[] shares;
							  uint public totalShares;
							  string public relayerName;
							  address registry;
							  WETH9 weth;

							  event Payout(address indexed token, uint amount);

						

Initialization Function



							  function init(address _registry, address[] shareholders, uint[] stakes,
							                address _weth, string _name) public returns (bool) {
							    require(totalShares == 0);
							    require(shareholders.length == stakes.length);
							    weth = WETH9(_weth);
							    totalShares = 0;
							    for(uint i=0; i < shareholders.length; i++) {
							        shares.push(Share({shareholder: shareholders[i], stake: stakes[i]}));
							        totalShares += stakes[i];
							    }
							    relayerName = _name;
							    registry = _registry;
							    return true;
							  }
							

Payout method


							  function payout(address[] tokens) public {
							      // Payout all stakes at once, so we don't have to do bookkeeping on who has
							      // claimed their shares and who hasn't. If the number of shareholders is large
							      // this could run into some gas limits. In most cases, I expect two
							      // shareholders, but it could be a small handful. This also means the caller
							      // must pay gas for everyone's payouts.
							      for(uint i=0; i < tokens.length; i++) {
							          ERC20 token = ERC20(tokens[i]);
							          uint balance = token.balanceOf(this);
							          for(uint j=0; j < shares.length; j++) {
							              token.transfer(
							                 shares[j].shareholder,
							                 SafeMath.mul(balance, shares[j].stake) / totalShares
							              );
							          }
							          emit Payout(tokens[i], balance);
							      }
							  }
							

fallback method


								function() public payable {
							    // If we get paid in ETH, convert to WETH so payouts work the same.
							    // Converting to WETH also makes payouts a bit safer, as we don't have to
							    // worry about code execution if the stakeholder is a contract.
							    weth.deposit.value(msg.value)();
							  }
							

isAffiliated method


							  function isAffiliated(address _affiliate) public returns (bool)
							  {
							      return Registry(registry).isAffiliated(_affiliate);
							  }
							}

							
## Affiliate Factory Contract * Written in Solidity

Boilerplate


								pragma solidity ^0.4.18;

								import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
								import 'zeppelin-solidity/contracts/math/SafeMath.sol';
								import './Affiliate.sol';

								contract AffiliateFactory is Ownable {

							

Contract Data



								event AffiliateDeployed(
								  address affiliateAddress,
								  address targetAddress,
								  string affiliateName,
								  address indexed sender
								);

								address public target;
								address public beneficiary;
								address public WETH;
								uint public beneficiaryStake;
								uint public senderStake;
								mapping(address => string) affiliates;

							

Constructor


								constructor(address _target, address _weth,
								            uint _beneficiaryStake, uint _senderStake) public Ownable() {
								   update(_target, msg.sender, _weth, _beneficiaryStake, _senderStake);
								}

								function update(address _target, address _beneficiary,
								                address _weth, uint _beneficiaryStake,
								                uint _senderStake) public onlyOwner {
								    target = _target;
								    beneficiary = _beneficiary;
								    beneficiaryStake = _beneficiaryStake;
								    senderStake = _senderStake;
								    WETH = _weth;
								}

							

Administrative Registration


								function registerAffiliate(address[] stakeHolders, uint[] shares,
								                           string _name)
								    external
								    onlyOwner
								    returns (address affiliateContract)
								{
								    affiliateContract = createProxyImpl(target);
								    require(Affiliate(affiliateContract).init(this, stakeHolders, shares,
								                                              WETH, _name));
								    affiliates[affiliateContract] = _name;
								    emit AffiliateDeployed(affiliateContract, target, _name, msg.sender);
								}
							

User Sign Up


								function signUp(address[] _stakeHolders, uint256[] _stakes, string _name)
									    external
									    returns (address affiliateContract)
									{
									    require(_stakeHolders.length > 0 &&
									            _stakeHolders.length == _stakes.length);
									    affiliateContract = createProxyImpl(target);
									    address[] memory stakeHolders = new address[](_stakeHolders.length + 1);
									    uint[] memory shares = new uint[](stakeHolders.length);
									    stakeHolders[0] = beneficiary;
									    shares[0] = beneficiaryStake;
									    uint256 stakesTotal = 0;

									    for(uint i=0; i < _stakeHolders.length; i++) {
									      require(_stakes[i] > 0);
									      stakesTotal = SafeMath.add(stakesTotal, _stakes[i]);
									    }
									    require(stakesTotal > 0);
									    for(i=0; i < _stakeHolders.length; i++) {
									      stakeHolders[i+1] = _stakeHolders[i];
									      // (user stake) / (total stake) * (available stake) ; but with integer math
									      shares[i+1] = (SafeMath.mul(_stakes[i], senderStake)
									                     / stakesTotal) ;
									    }
									    require(Affiliate(affiliateContract).init(
									        this, stakeHolders, shares, WETH, _name
									    ));
									    affiliates[affiliateContract] = _name;
									    emit AffiliateDeployed(
									        affiliateContract, target, _name, msg.sender
									    );
									}
							

Registry Function


								function isAffiliated(address _affiliate) external view returns (bool)
								{
									return bytes(affiliates[_affiliate]).length != 0;
								}

								function affiliateName(address _affiliate) external view returns (string)
								{
									return affiliates[_affiliate];
								}
							

Registry Function


								function createProxyImpl(address _target)
								    internal
								    returns (address proxyContract)
								{
								    assembly {
								        // Allocate 60 bytes of memory
								        let contractCode := mload(0x40)

								        // Write the target address from bytes 11 - 43
								        mstore(add(contractCode, 0x0b), _target)
								        // Write the first part of the contract from bytes 0 - 23
								        mstore(sub(contractCode, 0x09),
								          0x000000000000000000603160008181600b9039f3600080808080368092803773)
								        // Write the end of the contract from bytes 43 - 60
								        mstore(add(contractCode, 0x2b),
								          0x5af43d828181803e808314602f57f35bfd000000000000000000000000000000)

								        proxyContract := create(0, contractCode, 60) // total length 60 bytes
								        if iszero(extcodesize(proxyContract)) {
								            revert(0, 0)
								        }
								    }
								}
								/*
									0 PUSH1 0x31
									2 PUSH1 0x00
									4 DUP2
									5 DUP2
									6 PUSH1 0x0b
									8 SWAP1
									9 CODECOPY
									10 RETURN
									11 PUSH1 0x00
									13 DUP1
									14 DUP1
									15 DUP1
									16 DUP1
									17 CALLDATASIZE
									18 DUP1
									19 SWAP3
									20 DUP1
									21 CALLDATACOPY
									22 PUSH20 $TARGET_ADDRESS
									43 GAS
									44 DELEGATECALL
									45 RETURNDATASIZE
									46 DUP3
									47 DUP2
									48 DUP2
									49 DUP1
									50 RETURNDATACOPY
									51 DUP1
									52 DUP4
									53 EQ
									54 PUSH1 0x2f
									56 JUMPI
									57 RETURN
									58 JUMPDEST
									59 REVERT
								*/
							
## Proxy Contracts * 60 bytes total * Target contract is 2,945 bytes * 525,240 gas cheaper to deploy * At 20 gwei and $500 / ETH, that's about $5 savings * Target address hard coded in bytecode of proxy * Invoked as if it were the target contract * Affiliate contracts have separate storage and address
## Next Time * Affiliate Signup dApp * Affiliate Monitoring Microservice