|
| 1 | +;;; --------------------------------------------------------------------------- |
| 2 | +;;; Implementation of an ERC20 Token contract in LLL |
| 3 | +;;; |
| 4 | +;;; Ben Edgington - ben@benjaminion.xyz |
| 5 | +;;; |
| 6 | + |
| 7 | +(seq |
| 8 | + |
| 9 | + ;; -------------------------------------------------------------------------- |
| 10 | + ;; CONSTANTS |
| 11 | + |
| 12 | + ;; Token parameters. |
| 13 | + ;; 0x40 is a "magic number" - the text of the string is placed here |
| 14 | + ;; when returning the string to the caller. See return-string below. |
| 15 | + (def 'token-name-string (lit 0x40 "LLL Coin - love to code in LLL.")) |
| 16 | + (def 'token-symbol-string (lit 0x40 "LLL")) |
| 17 | + (def 'token-decimals 0) |
| 18 | + (def 'token-supply 100) ; 100 total tokens |
| 19 | + |
| 20 | + ;; Booleans |
| 21 | + (def 'false 0) |
| 22 | + (def 'true 1) |
| 23 | + |
| 24 | + ;; Memory layout. |
| 25 | + (def 'mem-ret 0x00) ; Fixed due to compiler macro for return. |
| 26 | + (def 'mem-func 0x00) ; No conflict with mem-ret, so re-use. |
| 27 | + (def 'mem-keccak 0x00) ; No conflict with mem-func or mem-ret, so re-use. |
| 28 | + (def 'scratch0 0x20) |
| 29 | + (def 'scratch1 0x40) |
| 30 | + |
| 31 | + ;; Precomputed function IDs. |
| 32 | + (def 'get-name 0x06fdde03) ; name() |
| 33 | + (def 'get-symbol 0x95d89b41) ; symbol() |
| 34 | + (def 'get-decimals 0x313ce567) ; decimals() |
| 35 | + (def 'get-total-supply 0x18160ddd) ; totalSupply() |
| 36 | + (def 'get-balance-of 0x70a08231) ; balanceOf(address) |
| 37 | + (def 'transfer 0xa9059cbb) ; transfer(address,uint256) |
| 38 | + (def 'transfer-from 0x23b872dd) ; transferFrom(address,address,uint256) |
| 39 | + (def 'approve 0x095ea7b3) ; approve(address,uint256) |
| 40 | + (def 'get-allowance 0xdd62ed3e) ; allowance(address,address) |
| 41 | + |
| 42 | + ;; Event IDs |
| 43 | + (def 'transfer-event-id ; Transfer(address,address,uint256) |
| 44 | + 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) |
| 45 | + |
| 46 | + (def 'approval-event-id ; Approval(address,address,uint256) |
| 47 | + 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925) |
| 48 | + |
| 49 | + ;; -------------------------------------------------------------------------- |
| 50 | + ;; UTILITIES |
| 51 | + |
| 52 | + ;; -------------------------------------------------------------------------- |
| 53 | + ;; The following define the key data-structures: |
| 54 | + ;; - balance(addr) => value |
| 55 | + ;; - allowance(addr,addr) => value |
| 56 | + |
| 57 | + ;; Balances are stored at s[owner_addr]. |
| 58 | + (def 'balance (address) address) |
| 59 | + |
| 60 | + ;; Allowances are stored at s[owner_addr + keccak256(spender_addr)] |
| 61 | + ;; We use a crypto function here to avoid any situation where |
| 62 | + ;; approve(me, spender) can be abused to do approve(target, me). |
| 63 | + (def 'allowance (owner spender) |
| 64 | + (seq |
| 65 | + (mstore mem-keccak spender) |
| 66 | + (add owner (keccak256 mem-keccak 0x20)))) |
| 67 | + |
| 68 | + ;; -------------------------------------------------------------------------- |
| 69 | + ;; For convenience we have macros to refer to function arguments |
| 70 | + |
| 71 | + (def 'arg1 (calldataload 0x04)) |
| 72 | + (def 'arg2 (calldataload 0x24)) |
| 73 | + (def 'arg3 (calldataload 0x44)) |
| 74 | + |
| 75 | + ;; -------------------------------------------------------------------------- |
| 76 | + ;; Revert is a soft return that does not consume the remaining gas. |
| 77 | + ;; We use it when rejecting invalid user input. |
| 78 | + ;; |
| 79 | + ;; Note: The REVERT opcode will be implemented in Metropolis (EIP 140). |
| 80 | + ;; Meanwhile it just causes an invalid instruction exception (similar |
| 81 | + ;; to a "throw" in Solidity). When fully implemented, Revert could be |
| 82 | + ;; use to return error codes, or even messages. |
| 83 | + |
| 84 | + (def 'revert () (revert 0 0)) |
| 85 | + |
| 86 | + ;; -------------------------------------------------------------------------- |
| 87 | + ;; Macro for returning string names. |
| 88 | + ;; Compliant with the ABI format for strings. |
| 89 | + |
| 90 | + (def 'return-string (string-literal) |
| 91 | + (seq |
| 92 | + (mstore 0x00 0x20) ; Points to our string's memory location |
| 93 | + (mstore 0x20 string-literal) ; Length. String itself is copied to 0x40. |
| 94 | + (return 0x00 (& (+ (mload 0x20) 0x5f) (~ 0x1f))))) |
| 95 | + ; Round return up to 32 byte boundary |
| 96 | + |
| 97 | + ;; -------------------------------------------------------------------------- |
| 98 | + ;; Convenience macro for raising Events |
| 99 | + |
| 100 | + (def 'event3 (id addr1 addr2 value) |
| 101 | + (seq |
| 102 | + (mstore scratch0 value) |
| 103 | + (log3 scratch0 0x20 id addr1 addr2))) |
| 104 | + |
| 105 | + ;; -------------------------------------------------------------------------- |
| 106 | + ;; Determines whether the stored function ID matches a known |
| 107 | + ;; function hash and executes <code-body> if so. |
| 108 | + ;; @param function-hash The four-byte hash of a known function signature. |
| 109 | + ;; @param code-body The code to run in the case of a match. |
| 110 | + |
| 111 | + (def 'function (function-hash code-body) |
| 112 | + (when (= (mload mem-func) function-hash) |
| 113 | + code-body)) |
| 114 | + |
| 115 | + ;; -------------------------------------------------------------------------- |
| 116 | + ;; Gets the function ID and stores it in memory for reference. |
| 117 | + ;; The function ID is in the leftmost four bytes of the call data. |
| 118 | + |
| 119 | + (def 'uses-functions |
| 120 | + (seq |
| 121 | + (mstore mem-func 0) |
| 122 | + (calldatacopy (+ mem-func 28) 0x00 4))) |
| 123 | + |
| 124 | + ;; -------------------------------------------------------------------------- |
| 125 | + ;; GUARDS |
| 126 | + |
| 127 | + ;; -------------------------------------------------------------------------- |
| 128 | + ;; Checks that ensure that each function is called with the right |
| 129 | + ;; number of arguments. For one thing this addresses the "ERC20 |
| 130 | + ;; short address attack". For another, it stops me making |
| 131 | + ;; mistakes while testing. We use these only on the non-constant functions. |
| 132 | + |
| 133 | + (def 'has-one-arg (unless (= 0x24 (calldatasize)) (revert))) |
| 134 | + (def 'has-two-args (unless (= 0x44 (calldatasize)) (revert))) |
| 135 | + (def 'has-three-args (unless (= 0x64 (calldatasize)) (revert))) |
| 136 | + |
| 137 | + ;; -------------------------------------------------------------------------- |
| 138 | + ;; Check that addresses have only 160 bits and revert if not. |
| 139 | + ;; We use these input type-checks on the non-constant functions. |
| 140 | + |
| 141 | + (def 'is-address (addr) |
| 142 | + (when |
| 143 | + (shr addr 160) |
| 144 | + (revert))) |
| 145 | + |
| 146 | + ;; -------------------------------------------------------------------------- |
| 147 | + ;; Check that transfer values are smaller than total supply and |
| 148 | + ;; revert if not. This should effectively exclude negative values. |
| 149 | + |
| 150 | + (def 'is-value (value) |
| 151 | + (when (> value token-supply) (revert))) |
| 152 | + |
| 153 | + ;; -------------------------------------------------------------------------- |
| 154 | + ;; Will revert if sent any Ether. We use the macro immediately so as |
| 155 | + ;; to abort if sent any Ether during contract deployment. |
| 156 | + |
| 157 | + (def 'not-payable |
| 158 | + (when (callvalue) (revert))) |
| 159 | + |
| 160 | + not-payable |
| 161 | + |
| 162 | + ;; -------------------------------------------------------------------------- |
| 163 | + ;; INITIALISATION |
| 164 | + ;; |
| 165 | + ;; Assign all tokens initially to the owner of the contract. |
| 166 | + |
| 167 | + (sstore (balance (caller)) token-supply) |
| 168 | + |
| 169 | + ;; -------------------------------------------------------------------------- |
| 170 | + ;; CONTRACT CODE |
| 171 | + |
| 172 | + (returnlll |
| 173 | + (seq not-payable uses-functions |
| 174 | + |
| 175 | + ;; ---------------------------------------------------------------------- |
| 176 | + ;; Getter for the name of the token. |
| 177 | + ;; @abi name() constant returns (string) |
| 178 | + ;; @return The token name as a string. |
| 179 | + |
| 180 | + (function get-name |
| 181 | + (return-string token-name-string)) |
| 182 | + |
| 183 | + ;; ---------------------------------------------------------------------- |
| 184 | + ;; Getter for the symbol of the token. |
| 185 | + ;; @abi symbol() constant returns (string) |
| 186 | + ;; @return The token symbol as a string. |
| 187 | + |
| 188 | + (function get-symbol |
| 189 | + (return-string token-symbol-string)) |
| 190 | + |
| 191 | + ;; ---------------------------------------------------------------------- |
| 192 | + ;; Getter for the number of decimals assigned to the token. |
| 193 | + ;; @abi decimals() constant returns (uint256) |
| 194 | + ;; @return The token decimals. |
| 195 | + |
| 196 | + (function get-decimals |
| 197 | + (return token-decimals)) |
| 198 | + |
| 199 | + ;; ---------------------------------------------------------------------- |
| 200 | + ;; Getter for the total token supply. |
| 201 | + ;; @abi totalSupply() constant returns (uint256) |
| 202 | + ;; @return The token supply. |
| 203 | + |
| 204 | + (function get-total-supply |
| 205 | + (return token-supply)) |
| 206 | + |
| 207 | + ;; ---------------------------------------------------------------------- |
| 208 | + ;; Returns the account balance of another account. |
| 209 | + ;; @abi balanceOf(address) constant returns (uint256) |
| 210 | + ;; @param owner The address of the account's owner. |
| 211 | + ;; @return The account balance. |
| 212 | + |
| 213 | + (function get-balance-of |
| 214 | + (seq |
| 215 | + |
| 216 | + (def 'owner arg1) |
| 217 | + |
| 218 | + (return (sload (balance owner))))) |
| 219 | + |
| 220 | + ;; ---------------------------------------------------------------------- |
| 221 | + ;; Transfers _value amount of tokens to address _to. The command |
| 222 | + ;; should throw if the _from account balance has not enough |
| 223 | + ;; tokens to spend. |
| 224 | + ;; @abi transfer(address, uint256) returns (bool) |
| 225 | + ;; @param to The account to receive the tokens. |
| 226 | + ;; @param value The quantity of tokens to transfer. |
| 227 | + ;; @return Success (true). Other outcomes result in a Revert. |
| 228 | + |
| 229 | + (function transfer |
| 230 | + (seq has-two-args (is-address arg1) (is-value arg2) |
| 231 | + |
| 232 | + (def 'to arg1) |
| 233 | + (def 'value arg2) |
| 234 | + |
| 235 | + (when value ; value == 0 is a no-op |
| 236 | + (seq |
| 237 | + |
| 238 | + ;; The caller's balance. Save in memory for efficiency. |
| 239 | + (mstore scratch0 (sload (balance (caller)))) |
| 240 | + |
| 241 | + ;; Revert if the caller's balance is not sufficient. |
| 242 | + (when (> value (mload scratch0)) |
| 243 | + (revert)) |
| 244 | + |
| 245 | + ;; Make the transfer |
| 246 | + ;; It would be good to check invariants (sum of balances). |
| 247 | + (sstore (balance (caller)) (- (mload scratch0) value)) |
| 248 | + (sstore (balance to) (+ (sload (balance to)) value)) |
| 249 | + |
| 250 | + ;; Event - Transfer(address,address,uint256) |
| 251 | + (event3 transfer-event-id (caller) to value))) |
| 252 | + |
| 253 | + (return true))) |
| 254 | + |
| 255 | + ;; ---------------------------------------------------------------------- |
| 256 | + ;; Send _value amount of tokens from address _from to address _to |
| 257 | + ;; @abi transferFrom(address,address,uint256) returns (bool) |
| 258 | + ;; @param from The account to send the tokens from. |
| 259 | + ;; @param to The account to receive the tokens. |
| 260 | + ;; @param value The quantity of tokens to transfer. |
| 261 | + ;; @return Success (true). Other outcomes result in a Revert. |
| 262 | + |
| 263 | + (function transfer-from |
| 264 | + (seq has-three-args (is-address arg1) (is-address arg2) (is-value arg3) |
| 265 | + |
| 266 | + (def 'from arg1) |
| 267 | + (def 'to arg2) |
| 268 | + (def 'value arg3) |
| 269 | + |
| 270 | + (when value ; value == 0 is a no-op |
| 271 | + |
| 272 | + (seq |
| 273 | + |
| 274 | + ;; Save data to memory for efficiency. |
| 275 | + (mstore scratch0 (sload (balance from))) |
| 276 | + (mstore scratch1 (sload (allowance from (caller)))) |
| 277 | + |
| 278 | + ;; Revert if not enough funds, or not enough approved. |
| 279 | + (when |
| 280 | + (|| |
| 281 | + (> value (mload scratch0)) |
| 282 | + (> value (mload scratch1))) |
| 283 | + (revert)) |
| 284 | + |
| 285 | + ;; Make the transfer and update allowance. |
| 286 | + (sstore (balance from) (- (mload scratch0) value)) |
| 287 | + (sstore (balance to) (+ (sload (balance to)) value)) |
| 288 | + (sstore (allowance from (caller)) (- (mload scratch1) value)) |
| 289 | + |
| 290 | + ;; Event - Transfer(address,address,uint256) |
| 291 | + (event3 transfer-event-id from to value))) |
| 292 | + |
| 293 | + (return true))) |
| 294 | + |
| 295 | + ;; ---------------------------------------------------------------------- |
| 296 | + ;; Allows _spender to withdraw from your account multiple times, |
| 297 | + ;; up to the _value amount. If this function is called again it |
| 298 | + ;; overwrites the current allowance with _value. |
| 299 | + ;; @abi approve(address,uint256) returns (bool) |
| 300 | + ;; @param spender The withdrawing account having its limit set. |
| 301 | + ;; @param value The maximum allowed amount. |
| 302 | + ;; @return Success (true). Other outcomes result in a Revert. |
| 303 | + |
| 304 | + (function approve |
| 305 | + (seq has-two-args (is-address arg1) (is-value arg2) |
| 306 | + |
| 307 | + (def 'spender arg1) |
| 308 | + (def 'value arg2) |
| 309 | + |
| 310 | + ;; Force users set the allowance to 0 before setting it to |
| 311 | + ;; another value for the same spender. Prevents this attack: |
| 312 | + ;; https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM |
| 313 | + (when |
| 314 | + (&& value (sload (allowance (caller) spender))) |
| 315 | + (revert)) |
| 316 | + |
| 317 | + (sstore (allowance (caller) spender) value) |
| 318 | + |
| 319 | + ;; Event - Approval(address,address,uint256) |
| 320 | + (event3 approval-event-id (caller) spender value) |
| 321 | + |
| 322 | + (return true))) |
| 323 | + |
| 324 | + ;; ---------------------------------------------------------------------- |
| 325 | + ;; Returns the amount which _spender is still allowed to withdraw |
| 326 | + ;; from _owner. |
| 327 | + ;; @abi allowance(address,address) constant returns (uint256) |
| 328 | + ;; @param owner The owning account. |
| 329 | + ;; @param spender The withdrawing account. |
| 330 | + ;; @return The allowed amount remaining. |
| 331 | + |
| 332 | + (function get-allowance |
| 333 | + (seq |
| 334 | + |
| 335 | + (def 'owner arg1) |
| 336 | + (def 'spender arg2) |
| 337 | + |
| 338 | + (return (sload (allowance owner spender))))) |
| 339 | + |
| 340 | + ;; ---------------------------------------------------------------------- |
| 341 | + ;; Fallback: No functions matched the function ID provided. |
| 342 | + |
| 343 | + (revert))) |
| 344 | + ) |
0 commit comments