Skip to content

Commit ff47999

Browse files
authored
Merge pull request #124 from zeppelinos/initializer_contracts_with_args
Initializer Contracts with arguments
2 parents 5cbd57e + 7c37f5a commit ff47999

File tree

12 files changed

+6823
-0
lines changed

12 files changed

+6823
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#Buidler files
2+
cache/
3+
artifacts/
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Initializer Contracts with arguments
2+
3+
## Context
4+
5+
The previous experiment ([`initializer_contracts`](/initializer_contracts)) demonstrated a way to
6+
store constructors on chain and use them directly, so as to avoid the shortcomings of manually
7+
written initializer functions. The constructors used did not have arguments, and we left that as a
8+
second challenge to be tackled. That is the purpose of this experiment.
9+
10+
## Understanding the challenge
11+
12+
Since transactions have only one data field, it must be used for both code and arguments in the
13+
case of contract creation: arguments must be concatenated after the code, and together are sent as
14+
data. Since during contract creation all transaction data becomes the code of the new contract, the
15+
constructor will read its arguments using `codecopy` (and may also use `codesize`).
16+
17+
Given this, the simple approach of deploying one reusable initializer contract that we used in the
18+
first experiment will not work for arguments. The contract will not have any arguments where the
19+
constructor code expects them, and those specified by the user cannot be placed in the already
20+
deployed code.
21+
22+
## Solution #1: Deploy a new contract
23+
24+
The simplest approach, and one that doesn't involve modifying code, is to redeploy the initializer
25+
contract every time it needs to be used, concatenated with the arguments specific to the instance
26+
that is being created.
27+
28+
This is implemented in [`scripts/poc-2.js`](/scripts/poc-2.js).
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module.exports = {
2+
};
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
pragma solidity ^0.4.24;
2+
3+
import './UpgradeabilityProxy.sol';
4+
5+
/**
6+
* @title AdminUpgradeabilityProxy
7+
* @dev This contract combines an upgradeability proxy with an authorization
8+
* mechanism for administrative tasks.
9+
* All external functions in this contract must be guarded by the
10+
* `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
11+
* feature proposal that would enable this to be done automatically.
12+
*/
13+
contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
14+
/**
15+
* @dev Emitted when the administration has been transferred.
16+
* @param previousAdmin Address of the previous admin.
17+
* @param newAdmin Address of the new admin.
18+
*/
19+
event AdminChanged(address previousAdmin, address newAdmin);
20+
21+
/**
22+
* @dev Storage slot with the admin of the contract.
23+
* This is the keccak-256 hash of "org.zeppelinos.proxy.admin", and is
24+
* validated in the constructor.
25+
*/
26+
bytes32 private constant ADMIN_SLOT = 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b;
27+
28+
/**
29+
* @dev Modifier to check whether the `msg.sender` is the admin.
30+
* If it is, it will run the function. Otherwise, it will delegate the call
31+
* to the implementation.
32+
*/
33+
modifier ifAdmin() {
34+
if (msg.sender == _admin()) {
35+
_;
36+
} else {
37+
_fallback();
38+
}
39+
}
40+
41+
/**
42+
* Contract constructor.
43+
* It sets the `msg.sender` as the proxy administrator.
44+
* @param _implementation address of the initial implementation.
45+
*/
46+
constructor(address _constructor, address _implementation, bytes _args)
47+
UpgradeabilityProxy(_constructor, _implementation, _args)
48+
public
49+
{
50+
assert(ADMIN_SLOT == keccak256("org.zeppelinos.proxy.admin"));
51+
52+
_setAdmin(msg.sender);
53+
}
54+
55+
/**
56+
* @return The address of the proxy admin.
57+
*/
58+
function admin() external view ifAdmin returns (address) {
59+
return _admin();
60+
}
61+
62+
/**
63+
* @return The address of the implementation.
64+
*/
65+
function implementation() external view ifAdmin returns (address) {
66+
return _implementation();
67+
}
68+
69+
/**
70+
* @dev Changes the admin of the proxy.
71+
* Only the current admin can call this function.
72+
* @param newAdmin Address to transfer proxy administration to.
73+
*/
74+
function changeAdmin(address newAdmin) external ifAdmin {
75+
require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
76+
emit AdminChanged(_admin(), newAdmin);
77+
_setAdmin(newAdmin);
78+
}
79+
80+
/**
81+
* @dev Upgrade the backing implementation of the proxy.
82+
* Only the admin can call this function.
83+
* @param newImplementation Address of the new implementation.
84+
*/
85+
function upgradeTo(address newImplementation) external ifAdmin {
86+
_upgradeTo(newImplementation);
87+
}
88+
89+
/**
90+
* @dev Upgrade the backing implementation of the proxy and call a function
91+
* on the new implementation.
92+
* This is useful to initialize the proxied contract.
93+
* @param newImplementation Address of the new implementation.
94+
* @param data Data to send as msg.data in the low level call.
95+
* It should include the signature and the parameters of the function to be
96+
* called, as described in
97+
* https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.
98+
*/
99+
function upgradeToAndCall(address newImplementation, bytes data) payable external ifAdmin {
100+
_upgradeTo(newImplementation);
101+
require(address(this).call.value(msg.value)(data));
102+
}
103+
104+
/**
105+
* @return The admin slot.
106+
*/
107+
function _admin() internal view returns (address adm) {
108+
bytes32 slot = ADMIN_SLOT;
109+
assembly {
110+
adm := sload(slot)
111+
}
112+
}
113+
114+
/**
115+
* @dev Sets the address of the proxy admin.
116+
* @param newAdmin Address of the new proxy admin.
117+
*/
118+
function _setAdmin(address newAdmin) internal {
119+
bytes32 slot = ADMIN_SLOT;
120+
121+
assembly {
122+
sstore(slot, newAdmin)
123+
}
124+
}
125+
126+
/**
127+
* @dev Only fall back when the sender is not the admin.
128+
*/
129+
function _willFallback() internal {
130+
require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
131+
super._willFallback();
132+
}
133+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pragma solidity ^0.4.21;
2+
3+
contract Greeter {
4+
5+
string public greeting;
6+
uint256 public created;
7+
8+
constructor(string _greeting) public {
9+
greeting = _greeting;
10+
created = block.number;
11+
}
12+
13+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
pragma solidity ^0.4.24;
2+
3+
import "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol";
4+
import "openzeppelin-solidity/contracts/token/ERC721/ERC721BasicToken.sol";
5+
6+
contract MyNFT is ERC721BasicToken, ERC721 {
7+
8+
// Token name
9+
string internal name_;
10+
11+
// Token symbol
12+
string internal symbol_;
13+
14+
// Mapping from owner to list of owned token IDs
15+
mapping(address => uint256[]) internal ownedTokens;
16+
17+
// Mapping from token ID to index of the owner tokens list
18+
mapping(uint256 => uint256) internal ownedTokensIndex;
19+
20+
// Array with all token ids, used for enumeration
21+
uint256[] internal allTokens;
22+
23+
// Mapping from token id to position in the allTokens array
24+
mapping(uint256 => uint256) internal allTokensIndex;
25+
26+
// Optional mapping for token URIs
27+
mapping(uint256 => string) internal tokenURIs;
28+
29+
/**
30+
* @dev Constructor function
31+
*/
32+
constructor(uint256 _initialId, string _name, string _symbol, uint8 _nothing) public {
33+
name_ = _name;
34+
symbol_ = _symbol;
35+
36+
_mint(msg.sender, _initialId);
37+
}
38+
39+
/**
40+
* @dev Gets the token name
41+
* @return string representing the token name
42+
*/
43+
function name() external view returns (string) {
44+
return name_;
45+
}
46+
47+
/**
48+
* @dev Gets the token symbol
49+
* @return string representing the token symbol
50+
*/
51+
function symbol() external view returns (string) {
52+
return symbol_;
53+
}
54+
55+
/**
56+
* @dev Returns an URI for a given token ID
57+
* Throws if the token ID does not exist. May return an empty string.
58+
* @param _tokenId uint256 ID of the token to query
59+
*/
60+
function tokenURI(uint256 _tokenId) public view returns (string) {
61+
require(exists(_tokenId));
62+
return tokenURIs[_tokenId];
63+
}
64+
65+
/**
66+
* @dev Gets the token ID at a given index of the tokens list of the requested owner
67+
* @param _owner address owning the tokens list to be accessed
68+
* @param _index uint256 representing the index to be accessed of the requested tokens list
69+
* @return uint256 token ID at the given index of the tokens list owned by the requested address
70+
*/
71+
function tokenOfOwnerByIndex(
72+
address _owner,
73+
uint256 _index
74+
)
75+
public
76+
view
77+
returns (uint256)
78+
{
79+
require(_index < balanceOf(_owner));
80+
return ownedTokens[_owner][_index];
81+
}
82+
83+
/**
84+
* @dev Gets the total amount of tokens stored by the contract
85+
* @return uint256 representing the total amount of tokens
86+
*/
87+
function totalSupply() public view returns (uint256) {
88+
return allTokens.length;
89+
}
90+
91+
/**
92+
* @dev Gets the token ID at a given index of all the tokens in this contract
93+
* Reverts if the index is greater or equal to the total number of tokens
94+
* @param _index uint256 representing the index to be accessed of the tokens list
95+
* @return uint256 token ID at the given index of the tokens list
96+
*/
97+
function tokenByIndex(uint256 _index) public view returns (uint256) {
98+
require(_index < totalSupply());
99+
return allTokens[_index];
100+
}
101+
102+
/**
103+
* @dev Internal function to set the token URI for a given token
104+
* Reverts if the token ID does not exist
105+
* @param _tokenId uint256 ID of the token to set its URI
106+
* @param _uri string URI to assign
107+
*/
108+
function _setTokenURI(uint256 _tokenId, string _uri) internal {
109+
require(exists(_tokenId));
110+
tokenURIs[_tokenId] = _uri;
111+
}
112+
113+
/**
114+
* @dev Internal function to add a token ID to the list of a given address
115+
* @param _to address representing the new owner of the given token ID
116+
* @param _tokenId uint256 ID of the token to be added to the tokens list of the given address
117+
*/
118+
function addTokenTo(address _to, uint256 _tokenId) internal {
119+
super.addTokenTo(_to, _tokenId);
120+
uint256 length = ownedTokens[_to].length;
121+
ownedTokens[_to].push(_tokenId);
122+
ownedTokensIndex[_tokenId] = length;
123+
}
124+
125+
/**
126+
* @dev Internal function to remove a token ID from the list of a given address
127+
* @param _from address representing the previous owner of the given token ID
128+
* @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address
129+
*/
130+
function removeTokenFrom(address _from, uint256 _tokenId) internal {
131+
super.removeTokenFrom(_from, _tokenId);
132+
133+
// To prevent a gap in the array, we store the last token in the index of the token to delete, and
134+
// then delete the last slot.
135+
uint256 tokenIndex = ownedTokensIndex[_tokenId];
136+
uint256 lastTokenIndex = ownedTokens[_from].length.sub(1);
137+
uint256 lastToken = ownedTokens[_from][lastTokenIndex];
138+
139+
ownedTokens[_from][tokenIndex] = lastToken;
140+
ownedTokens[_from].length--; // This also deletes the contents at the last position of the array
141+
142+
// Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to
143+
// be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping
144+
// the lastToken to the first position, and then dropping the element placed in the last position of the list
145+
146+
ownedTokensIndex[_tokenId] = 0;
147+
ownedTokensIndex[lastToken] = tokenIndex;
148+
}
149+
150+
/**
151+
* @dev Internal function to mint a new token
152+
* Reverts if the given token ID already exists
153+
* @param _to address the beneficiary that will own the minted token
154+
* @param _tokenId uint256 ID of the token to be minted by the msg.sender
155+
*/
156+
function _mint(address _to, uint256 _tokenId) internal {
157+
super._mint(_to, _tokenId);
158+
159+
allTokensIndex[_tokenId] = allTokens.length;
160+
allTokens.push(_tokenId);
161+
}
162+
163+
/**
164+
* @dev Internal function to burn a specific token
165+
* Reverts if the token does not exist
166+
* @param _owner owner of the token to burn
167+
* @param _tokenId uint256 ID of the token being burned by the msg.sender
168+
*/
169+
function _burn(address _owner, uint256 _tokenId) internal {
170+
super._burn(_owner, _tokenId);
171+
172+
// Clear metadata (if any)
173+
if (bytes(tokenURIs[_tokenId]).length != 0) {
174+
delete tokenURIs[_tokenId];
175+
}
176+
177+
// Reorg all tokens array
178+
uint256 tokenIndex = allTokensIndex[_tokenId];
179+
uint256 lastTokenIndex = allTokens.length.sub(1);
180+
uint256 lastToken = allTokens[lastTokenIndex];
181+
182+
allTokens[tokenIndex] = lastToken;
183+
allTokens[lastTokenIndex] = 0;
184+
185+
allTokens.length--;
186+
allTokensIndex[_tokenId] = 0;
187+
allTokensIndex[lastToken] = tokenIndex;
188+
}
189+
190+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import "openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
2+
3+
contract MyToken is StandardToken {
4+
string public name;
5+
string public symbol;
6+
uint8 public decimals;
7+
8+
constructor(uint256 _initialSupply, string _name, string _symbol, uint8 _decimals) public {
9+
name = _name;
10+
symbol = _symbol;
11+
decimals = _decimals;
12+
13+
totalSupply_ = _initialSupply;
14+
balances[msg.sender] = _initialSupply;
15+
emit Transfer(address(0), msg.sender, _initialSupply);
16+
}
17+
}

0 commit comments

Comments
 (0)