Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Jamiebones
Jamiebones

Posted on

     

Design Patterns In Solidity

Design pattern are the common patterns that could be applied when designing a contract in Solidity. Some commonly applied patterns in smart contract are :

  • The withdrawal pattern to withdraw ether from a contract
  • The factory contract pattern to create new contracts
  • The Iterable map pattern that allows looping over a mapping.
  • The tight variable packaging to reduce gas consumption when using structs.

We will discuss about the following design patterns in this post:

Withdrawal pattern

The withdrawal pattern is also referred as apull-over-push pattern. This pattern allows ether or token to be removed from a contract by pull instead of push.

A smart contract can contain ether and you might want to distribute the ether to different account. It is better you allow the owner of the ether or token pull it themselves instead of pushing the ether to their address.

Sample code:

contractTokenBank{addresss[]internalinvestors;mapping(address=>uint256)internalbalances;//function saves an investor address on an arrayfunctionregisterInvestor(address_investor)publiconlyOnwer{require(_investor!=address(0));investors.push(_investor);}functioncalulateDividendAccured(address_investor)internalreturns(uint256){//perform your calculations here and return the dividends}//bad practicefunctiondistributeDividends()publiconlyOwner{for(uinti=0;i<investors.length;i++){uintamount=calulateDividendAccured(investors[i]);//amount is what due to the investorbalances[investor[i]]=0;investors[i].transfer(amount);//pushing ether to address}}}
Enter fullscreen modeExit fullscreen mode

The example above is a contract where investors address are saved in an address [] and dividends are calculated and withdrawn from the contract and paid to the investor.

The problem with this pattern is that :

  • we are looping over an unbounded array whose size might grow very big with time and we are also changing the state variables which might exceed the block gas limits and makes the transactions fail, causing the locking of ether in the contract.

  • the contract owner is saddled with the responsibilities of paying for the gas consumed by the transaction. Why not outsource that responsibility to the investor, who wants to withdraw dividends.

The withdrawal pattern specifies that you create a function which the user can call when they want to withdraw their dividends. By this way, you avoid looping over an array that could easily grow out of bounds

//Good practicefunctionclaimDividend()public{uintamount=calulateDividendAccured[msg.sender];require(amount>0,"No dividends to claim");//set their balance to zero to prevent reentrancy attackbalances[msg.sender]=0;msg.sender.transfer(amount);//pull ether from thecontract}
Enter fullscreen modeExit fullscreen mode

When to use the withdrawal pattern

  • You are sending ether/token to multiple accounts
  • You want to avoid paying transaction fee as the initiator of the transaction pays the fees.

Access restriction pattern

This pattern as the name entails is one that is used when restriction is necessary on the contract. It consist of the use ofmodifiers to restrict access to some functions on a contract.

sample code

contractLibraryManagementControl{addresspublicowner;addresspubliclibarian;constructor(){owner=msg.sender;}functionappointLibarian(address_libAddress)publiconlyOwner{libarian=_libAddress;}modifieronlyLibarian{require(msg.sender==libarian);_;}modifieronlyOwner{require(msg.sender==owner);_;}modifierauthorized{require(msg.sender==owner||msg.sender==libarian);_;}functionsetUpLibary()publiconlyLibarian{//code to set up a library}}
Enter fullscreen modeExit fullscreen mode

The code above is a simple contract that sets up a library. The person's that deploys the contract becomes the contract owner and he is authorized to appoint a librarian. The pattern makes use of access modifiers to restrict some certain functions in the contract from being called by those not authorized.

When to use

  • some functions are only to be executed from certain roles
  • you want to improve the security of the contracts from unauthorized function calls.

Emergency stop pattern

This pattern incorporates the ability to pause all function execution in the contract in the case of something going wrong. Since contracts are immutable once deployed; if there are bugs in the contract thepause functionality can be activated to prevent further damage caused.

The emergency pause functionality should be in the control of the owner or an authorized address. TheEmergency stop pattern should be avoided at all means as it is against the spirit of decentralisation but in those instance where a centralized control is required, it could be a worthy pattern to incorporate in your contract.

Sample code :

// SPDX-License-Identifier: MITpragmasolidity^0.8.10;contractMerchantBank{addresspayablepublicowner;boolpaused=false;constructor(){owner=payable(msg.sender);}modifieronlyOwner{require(msg.sender==owner);_;}modifierisPaused{require(paused);_;}modifiernotPaused{require(!paused);_;}functionpauseContract()publiconlyOwnernotPaused{paused=true;}functionunpauseContract()publiconlyOwnerisPaused{paused=false;}functiondepositEther()publicnotPaused{//logic to deposit ether into the contract}functionemergencyWithdrawal()publicisPaused{//transfer the ether out fast before more damage is doneowner.transfer(address(this).balance);}}
Enter fullscreen modeExit fullscreen mode

Our sample code has a contract calledMerchantBank which the owner of the contract is the person that deploys it. Access modifiers are used to restrict access. The contract has a function calleddepositEther which can only be called when the function is not in a paused state. The pause state can be activated by the contract owner and when the contract is paused users can no longer deposits ether.

The contract also has anemergencyWithdrawal function that can be triggered by the owner if the contract is paused as a result of bugs in the code and the ether in the contract can safely be transferred elsewhere.

When to use

  • You want the ability to pause the contract as a result of unwanted situation
  • In case of failure you want the ability to stop state corruption.
  • You are a centralized entity.

Factory creation pattern

This pattern is used for the creation of child contract from a parent that acts as a factory. The factory contract has an array of address where all the child contracts created are stored. This pattern is very common in Solidity.

Sample code:

// SPDX-License-Identifier: MITpragmasolidity^0.8.10;contractOTokenFactory{address[]publicotokenAddress;functioncreateNewOToken(address_asset,address_collateral,address_strikePrice,uint256_expiry)publicreturns(address){addressotoken=address(newOToken(_asset,_collateral,_expiry,_strikePrice));otokenAddress.push(otoken);returnotoken;}}contractOToken{addresspublicasset;addresspubliccollateral;addresspublicstrikePrice;uint256publicexpiry;constructor(address_asset,address_collateral,uint256_expiry,address_strikePrice){asset=_asset;collateral=_collateral;strikePrice=_strikePrice;expiry=_expiry;}}
Enter fullscreen modeExit fullscreen mode

The code above is used for the creation of oToken. The contract OTokenFactory is deployed and it is used to create new child OToken contract. When the functioncreateNewOToken is called, parameters that is used for creating a new OToken is passed to it.

The function uses thenew keyword to spawn a new instance ofOToken contract. The result of this line below :

address otoken = address (new OToken(_asset, _collateral, _expiry,
_strikePrice));
otokenAddress.push(otoken);

is an address, that is stored in an array of addresses that depicts the address of the new created OToken contract.

When to use

  • You need a new contract for each request.

Iterable map pattern

The iterable map pattern allows you to iterate over mapping entries. Solidity mapping provides no way to iterate over them. There are some cases where you will need to iterate over a mapping. This pattern is well situated for such cases.

// SPDX-License-Identifier: MITpragmasolidity^0.8.10;contractDepositContract{mapping(address=>uint256)publicbalances;address[]publicaccountHolders;functiondeposit()payablepublic{require(msg.value>0);boolexists=balances[msg.sender]!=0;if(!exists){accountHolders.push(msg.sender);}balances[msg.sender]+=msg.value;}structAccountDetails{addressaccountAddress;uint256amount;}functiongetAccountHolders()publicviewreturns(AccountDetails[]memory){AccountDetails[]memoryaccounts=newAccountDetails[](accountHolders.length);for(uint256i=0;i<accounts.length;i++){address_currentAddress=accountHolders[i];uint256_amount=balances[_currentAddress];accounts[i]=AccountDetails(_currentAddress,_amount);}returnaccounts;}}
Enter fullscreen modeExit fullscreen mode

In this pattern we added the ability to loop over our data in the mapping data structure. The contract above calledDepositContract consists of a mapping that links the address to the amount deposited by each address. Normally without this pattern, we could only pull out the value deposited by supplying the address.

Thedeposit function that takes care of the amount deposited by the user stores that value in a state variable calledbalances. The function also stores the address of the depositor into an address array calledaccountHolders. The code checks and ensures that only unique address are saved in theaccountHolders array.

There is another function calledgetAccountHolders and it's purpose is to display the address and the value owned by each address. The function is a view function which does not require a transaction, thereby saving on gas. We defined a struct that contains an address and a uint256 variables, used for saving the account details.

We created an array ofAccountDetails struct in memory calledaccounts that will hold each account details. We initialized the value of accounts to the length of theaccountHolders array (contains unique address of account holders). As we loop through theaccountHolders array we retrieve the saved address which we plug into thebalances mapping to retrieve the amount saved by that address. These value and the address is placed in aAccountDetails struct and pushed into theaccounts array that holds the array of AccountDetails.

As we can see, this pattern allows us to iterate over all values in a mapping.

When to use

  • You need iterable behaviours over Solidity mappings
  • The items contain in the mappings are not that many.
  • You would want to filter some data out of the mapping and get the result via a view function.

Summary

There are many more patterns in the wild. This is just a scratch on what is available. I hope you have been able to learn a thing or two.

Thanks for reading..

Happy coding!

Top comments(4)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
jofawole profile image
johnfawole.sol
  • Joined

I've always learned one or two things from your posts. Kudos, man.

Please, what's your Twitter handle? I'd like to connect with you on Twitter.

CollapseExpand
 
jamiescript profile image
Jamiebones
Javascript developer working hard to master the craft.
  • Email
  • Location
    Uyo, Nigeria
  • Education
    University of Ilorin
  • Work
    Author @ Educative.io
  • Joined
CollapseExpand
 
gitcommitshow profile image
gitcommitshow
  • Joined

Awesome. It would make the article easier to read if you can add the list of all the patterns discussed here on top including one line explanation of each. And then link them to the detailed explanation.

I'd want to come back to it when I'm working on such project.

CollapseExpand
 
jamiescript profile image
Jamiebones
Javascript developer working hard to master the craft.
  • Email
  • Location
    Uyo, Nigeria
  • Education
    University of Ilorin
  • Work
    Author @ Educative.io
  • Joined

Thanks for the suggestion. I have updated the post.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Javascript developer working hard to master the craft.
  • Location
    Uyo, Nigeria
  • Education
    University of Ilorin
  • Work
    Author @ Educative.io
  • Joined

More fromJamiebones

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp