Add contracts to packages, fix most linting errors

This commit is contained in:
Amir Bandeali
2017-11-29 22:02:43 -08:00
parent c57190dead
commit 4b3e038323
104 changed files with 14544 additions and 27 deletions

1
.gitignore vendored
View File

@@ -68,3 +68,4 @@ generated_docs/
TODO.md
packages/website/public/bundle*
transpiled/

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,394 @@
{
"contract_name": "EtherToken",
"abi": [
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "amount",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "deposit",
"outputs": [],
"payable": true,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"payable": true,
"type": "fallback"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b5b61070f8061001c6000396000f300606060405236156100935763ffffffff60e060020a60003504166306fdde0381146100a4578063095ea7b31461013457806318160ddd1461016757806323b872dd146101895780632e1a7d4d146101c2578063313ce567146101d757806370a08231146101fd57806395d89b411461022b578063a9059cbb146102bb578063d0e30db0146102ee578063dd62ed3e146102f8575b6100a25b61009f61032c565b5b565b005b34156100ac57fe5b6100b461037b565b6040805160208082528351818301528351919283929083019185019080838382156100fa575b8051825260208311156100fa57601f1990920191602091820191016100da565b505050905090810190601f1680156101265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561013c57fe5b610153600160a060020a03600435166024356103a3565b604080519115158252519081900360200190f35b341561016f57fe5b61017761040e565b60408051918252519081900360200190f35b341561019157fe5b610153600160a060020a0360043581169060243516604435610414565b604080519115158252519081900360200190f35b34156101ca57fe5b6100a26004356104ff565b005b34156101df57fe5b6101e7610580565b6040805160ff9092168252519081900360200190f35b341561020557fe5b610177600160a060020a0360043516610585565b60408051918252519081900360200190f35b341561023357fe5b6100b46105a4565b6040805160208082528351818301528351919283929083019185019080838382156100fa575b8051825260208311156100fa57601f1990920191602091820191016100da565b505050905090810190601f1680156101265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156102c357fe5b610153600160a060020a03600435166024356105c5565b604080519115158252519081900360200190f35b6100a261032c565b005b341561030057fe5b610177600160a060020a0360043581169060243516610665565b60408051918252519081900360200190f35b600160a060020a03331660009081526020819052604090205461034f9034610692565b600160a060020a0333166000908152602081905260409020556002546103759034610692565b6002555b565b60408051808201909152600b815260a960020a6a22ba3432b9102a37b5b2b702602082015281565b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a03831660009081526020819052604081205461043790836106ac565b600160a060020a03808616600090815260208181526040808320949094556001815283822033909316825291909152205461047290836106ac565b600160a060020a038086166000908152600160209081526040808320338516845282528083209490945591861681529081905220546104b19083610692565b600160a060020a038085166000818152602081815260409182902094909455805186815290519193928816926000805160206106c483398151915292918290030190a35060015b9392505050565b600160a060020a03331660009081526020819052604090205461052290826106ac565b600160a060020a03331660009081526020819052604090205560025461054890826106ac565b600255604051600160a060020a0333169082156108fc029083906000818181858888f19350505050151561057c5760006000fd5b5b50565b601281565b600160a060020a0381166000908152602081905260409020545b919050565b604080518082019091526004815260e360020a630ae8aa8902602082015281565b600160a060020a0333166000908152602081905260408120546105e890836106ac565b600160a060020a0333811660009081526020819052604080822093909355908516815220546106179083610692565b600160a060020a03808516600081815260208181526040918290209490945580518681529051919333909316926000805160206106c483398151915292918290030190a35060015b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b6000828201838110156106a157fe5b8091505b5092915050565b6000828211156106b857fe5b508082035b929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a72305820b0be668913b36ff13a97f9ad56d8eb2e9c169cc9b32bfde9e3b92cec1036f1080029",
"networks": {
"50": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
},
"updated_at": 1502391794392,
"address": "0x6eead871b92e216b0368f596e751a25841f65bec"
},
"42": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
},
"updated_at": 1502391794392,
"address": "0x05d090b51c40b020eab3bfcb6a2dff130df22e9c"
},
"1": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
},
"updated_at": 1502488087000,
"address": "0x2956356cd2a2bf3202f771f50d3d14a367b48070"
}
},
"schema_version": "0.0.5",
"updated_at": 1502391794392
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,227 @@
{
"contract_name": "MaliciousToken",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
],
"unlinked_binary": "0x60606040526003805460ff19166001179055341561001957fe5b5b610467806100296000396000f3006060604052361561005c5763ffffffff60e060020a600035041663095ea7b3811461005e57806318160ddd1461009157806323b872dd146100b357806370a08231146100ec578063a9059cbb1461011a578063dd62ed3e1461014d575bfe5b341561006657fe5b61007d600160a060020a0360043516602435610181565b604080519115158252519081900360200190f35b341561009957fe5b6100a16101ec565b60408051918252519081900360200190f35b34156100bb57fe5b61007d600160a060020a03600435811690602435166044356101f2565b604080519115158252519081900360200190f35b34156100f457fe5b6100a1600160a060020a03600435166102ee565b60408051918252519081900360200190f35b341561012257fe5b61007d600160a060020a0360043516602435610318565b604080519115158252519081900360200190f35b341561015557fe5b6100a1600160a060020a03600435811690602435166103ca565b60408051918252519081900360200190f35b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a0383166000908152602081905260408120548290108015906102425750600160a060020a0380851660009081526001602090815260408083203390941683529290522054829010155b80156102685750600160a060020a03831660009081526020819052604090205482810110155b156102e257600160a060020a03808416600081815260208181526040808320805488019055888516808452818420805489900390556001835281842033909616845294825291829020805487900390558151868152915192939260008051602061041c8339815191529281900390910190a35060016102e6565b5060005b5b9392505050565b60006102f8610402565b50600160a060020a0381166000908152602081905260409020545b919050565b600160a060020a03331660009081526020819052604081205482901080159061035b5750600160a060020a03831660009081526020819052604090205482810110155b156103bb57600160a060020a03338116600081815260208181526040808320805488900390559387168083529184902080548701905583518681529351919360008051602061041c833981519152929081900390910190a35060016101e6565b5060006101e6565b5b92915050565b60006103d4610402565b50600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b6003805460ff8082166001011660ff199091161790555b5600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a723058201238dbb2f96252f5d35040796309238210d3a5d9ac3261d0effff444e8f0cd0c0029",
"networks": {
"50": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
},
"updated_at": 1502391794396
}
},
"schema_version": "0.0.5",
"updated_at": 1502391794396
}

View File

@@ -0,0 +1,72 @@
{
"contract_name": "Migrations",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "new_address",
"type": "address"
}
],
"name": "upgrade",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "last_completed_migration",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "owner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "completed",
"type": "uint256"
}
],
"name": "setCompleted",
"outputs": [],
"payable": false,
"type": "function"
},
{
"inputs": [],
"payable": false,
"type": "constructor"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b5b60008054600160a060020a03191633600160a060020a03161790555b5b6101a0806100396000396000f300606060405263ffffffff60e060020a6000350416630900f0108114610042578063445df0ac146100605780638da5cb5b14610082578063fdacd576146100ae575bfe5b341561004a57fe5b61005e600160a060020a03600435166100c3565b005b341561006857fe5b61007061013d565b60408051918252519081900360200190f35b341561008a57fe5b610092610143565b60408051600160a060020a039092168252519081900360200190f35b34156100b657fe5b61005e600435610152565b005b6000805433600160a060020a03908116911614156101375781905080600160a060020a031663fdacd5766001546040518263ffffffff1660e060020a02815260040180828152602001915050600060405180830381600087803b151561012557fe5b6102c65a03f1151561013357fe5b5050505b5b5b5050565b60015481565b600054600160a060020a031681565b60005433600160a060020a039081169116141561016f5760018190555b5b5b505600a165627a7a72305820721709a2522264b5277c3048b17bea0e0f660776a386bacb5f36796ba40dac1c0029",
"networks": {
"50": {
"links": {},
"events": {},
"updated_at": 1502391794384
}
},
"schema_version": "0.0.5",
"updated_at": 1502391794384
}

View File

@@ -0,0 +1,189 @@
{
"contract_name": "Mintable",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_value",
"type": "uint256"
}
],
"name": "mint",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b5b6104da8061001c6000396000f300606060405236156100675763ffffffff60e060020a600035041663095ea7b3811461006957806318160ddd1461009c57806323b872dd146100be57806370a08231146100f7578063a0712d6814610125578063a9059cbb1461013a578063dd62ed3e1461016d575bfe5b341561007157fe5b610088600160a060020a03600435166024356101a1565b604080519115158252519081900360200190f35b34156100a457fe5b6100ac61020c565b60408051918252519081900360200190f35b34156100c657fe5b610088600160a060020a0360043581169060243516604435610212565b604080519115158252519081900360200190f35b34156100ff57fe5b6100ac600160a060020a036004351661030e565b60408051918252519081900360200190f35b341561012d57fe5b61013860043561032d565b005b341561014257fe5b610088600160a060020a0360043516602435610395565b604080519115158252519081900360200190f35b341561017557fe5b6100ac600160a060020a0360043581169060243516610447565b60408051918252519081900360200190f35b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a0383166000908152602081905260408120548290108015906102625750600160a060020a0380851660009081526001602090815260408083203390941683529290522054829010155b80156102885750600160a060020a03831660009081526020819052604090205482810110155b1561030257600160a060020a03808416600081815260208181526040808320805488019055888516808452818420805489900390556001835281842033909616845294825291829020805487900390558151868152915192939260008051602061048f8339815191529281900390910190a3506001610306565b5060005b5b9392505050565b600160a060020a0381166000908152602081905260409020545b919050565b68056bc75e2d631000008111156103445760006000fd5b600160a060020a033316600090815260208190526040902054610368908290610474565b600160a060020a03331660009081526020819052604090205560025461038e9082610474565b6002555b50565b600160a060020a0333166000908152602081905260408120548290108015906103d85750600160a060020a03831660009081526020819052604090205482810110155b1561043857600160a060020a03338116600081815260208181526040808320805488900390559387168083529184902080548701905583518681529351919360008051602061048f833981519152929081900390910190a3506001610206565b506000610206565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b60008282018381101561048357fe5b8091505b50929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a72305820fb9e3b0567bae493373766d7c9980e78be7513e22369cbce495956e5849c284d0029",
"networks": {},
"schema_version": "0.0.5",
"updated_at": 1502391449723
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
{
"contract_name": "Ownable",
"abi": [
{
"constant": true,
"inputs": [],
"name": "owner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"payable": false,
"type": "function"
},
{
"inputs": [],
"payable": false,
"type": "constructor"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b5b60008054600160a060020a03191633600160a060020a03161790555b5b60f3806100386000396000f300606060405263ffffffff60e060020a6000350416638da5cb5b8114602a578063f2fde38b146053575bfe5b3415603157fe5b6037606e565b60408051600160a060020a039092168252519081900360200190f35b3415605a57fe5b606c600160a060020a0360043516607d565b005b600054600160a060020a031681565b60005433600160a060020a0390811691161460985760006000fd5b600160a060020a0381161560c25760008054600160a060020a031916600160a060020a0383161790555b5b5b505600a165627a7a7230582009515308d7738f55dd8ba99e29943dc01d4a032197af5f4e1adc20b93fb927f20029",
"networks": {},
"schema_version": "0.0.5",
"updated_at": 1502391792217
}

View File

@@ -0,0 +1,8 @@
{
"contract_name": "SafeMath",
"abi": [],
"unlinked_binary": "0x60606040523415600b57fe5b5b60338060196000396000f30060606040525bfe00a165627a7a723058201f432ae32cd7cc9a0efb0b70b25524cf42d165202875a497f8b0122d77a6a5ba0029",
"networks": {},
"schema_version": "0.0.5",
"updated_at": 1502391792217
}

View File

@@ -0,0 +1,176 @@
{
"contract_name": "StandardToken",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b5b6104388061001c6000396000f3006060604052361561005c5763ffffffff60e060020a600035041663095ea7b3811461005e57806318160ddd1461009157806323b872dd146100b357806370a08231146100ec578063a9059cbb1461011a578063dd62ed3e1461014d575bfe5b341561006657fe5b61007d600160a060020a0360043516602435610181565b604080519115158252519081900360200190f35b341561009957fe5b6100a16101ec565b60408051918252519081900360200190f35b34156100bb57fe5b61007d600160a060020a03600435811690602435166044356101f2565b604080519115158252519081900360200190f35b34156100f457fe5b6100a1600160a060020a03600435166102ee565b60408051918252519081900360200190f35b341561012257fe5b61007d600160a060020a036004351660243561030d565b604080519115158252519081900360200190f35b341561015557fe5b6100a1600160a060020a03600435811690602435166103bf565b60408051918252519081900360200190f35b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a0383166000908152602081905260408120548290108015906102425750600160a060020a0380851660009081526001602090815260408083203390941683529290522054829010155b80156102685750600160a060020a03831660009081526020819052604090205482810110155b156102e257600160a060020a0380841660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293926000805160206103ed8339815191529281900390910190a35060016102e6565b5060005b5b9392505050565b600160a060020a0381166000908152602081905260409020545b919050565b600160a060020a0333166000908152602081905260408120548290108015906103505750600160a060020a03831660009081526020819052604090205482810110155b156103b057600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191936000805160206103ed833981519152929081900390910190a35060016101e6565b5060006101e6565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a723058208bbb664bbcb187b9a351ec93e9763b53c0157b40e7ee3033bb5b59eddd9e575e0029",
"networks": {},
"schema_version": "0.0.5",
"updated_at": 1502391449723
}

View File

@@ -0,0 +1,176 @@
{
"contract_name": "Token",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "supply",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "remaining",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b5b6101e08061001c6000396000f3006060604052361561005c5763ffffffff60e060020a600035041663095ea7b3811461005e57806318160ddd1461009157806323b872dd146100b357806370a08231146100ec578063a9059cbb1461005e578063dd62ed3e1461014d575bfe5b341561006657fe5b61007d600160a060020a0360043516602435610181565b604080519115158252519081900360200190f35b341561009957fe5b6100a161018a565b60408051918252519081900360200190f35b34156100bb57fe5b61007d600160a060020a0360043581169060243516604435610190565b604080519115158252519081900360200190f35b34156100f457fe5b6100a1600160a060020a036004351661019a565b60408051918252519081900360200190f35b341561006657fe5b61007d600160a060020a0360043516602435610181565b604080519115158252519081900360200190f35b341561015557fe5b6100a1600160a060020a0360043581169060243516610181565b60408051918252519081900360200190f35b60005b92915050565b60005b90565b60005b9392505050565b60005b919050565b60005b92915050565b60005b929150505600a165627a7a7230582082d46fcd9caa49348b3932d5c18807e1d10d0105371a8bce147cc647f3762a500029",
"networks": {},
"schema_version": "0.0.5",
"updated_at": 1502391792217
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,298 @@
{
"contract_name": "TokenTransferProxy",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "token",
"type": "address"
},
{
"name": "from",
"type": "address"
},
{
"name": "to",
"type": "address"
},
{
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "target",
"type": "address"
}
],
"name": "addAuthorizedAddress",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "uint256"
}
],
"name": "authorities",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "target",
"type": "address"
}
],
"name": "removeAuthorizedAddress",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "owner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "address"
}
],
"name": "authorized",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getAuthorizedAddresses",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"payable": false,
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressRemoved",
"type": "event"
}
],
"unlinked_binary": "0x60606040525b60008054600160a060020a03191633600160a060020a03161790555b5b6106e6806100316000396000f300606060405236156100725763ffffffff60e060020a60003504166315dacbea811461007457806342f1181e146100b3578063494503d4146100d157806370712939146101005780638da5cb5b1461011e578063b91816111461014a578063d39de6e91461017a578063f2fde38b146101e5575bfe5b341561007c57fe5b61009f600160a060020a0360043581169060243581169060443516606435610203565b604080519115158252519081900360200190f35b34156100bb57fe5b6100cf600160a060020a03600435166102ae565b005b34156100d957fe5b6100e4600435610390565b60408051600160a060020a039092168252519081900360200190f35b341561010857fe5b6100cf600160a060020a03600435166103c2565b005b341561012657fe5b6100e461055a565b60408051600160a060020a039092168252519081900360200190f35b341561015257fe5b61009f600160a060020a0360043516610569565b604080519115158252519081900360200190f35b341561018257fe5b61018a61057e565b60408051602080825283518183015283519192839290830191858101910280838382156101d2575b8051825260208311156101d257601f1990920191602091820191016101b2565b5050509050019250505060405180910390f35b34156101ed57fe5b6100cf600160a060020a03600435166105e7565b005b600160a060020a03331660009081526001602052604081205460ff16151561022b5760006000fd5b6040805160006020918201819052825160e060020a6323b872dd028152600160a060020a0388811660048301528781166024830152604482018790529351938916936323b872dd9360648084019491938390030190829087803b151561028d57fe5b6102c65a03f1151561029b57fe5b5050604051519150505b5b949350505050565b60005433600160a060020a039081169116146102ca5760006000fd5b600160a060020a038116600090815260016020526040902054819060ff16156102f35760006000fd5b600160a060020a0382166000908152600160208190526040909120805460ff191682179055600280549091810161032a8382610633565b916000526020600020900160005b81546101009190910a600160a060020a0381810219909216868316918202179092556040513390911692507f94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca90600090a35b5b505b50565b600280548290811061039e57fe5b906000526020600020900160005b915054906101000a9004600160a060020a031681565b6000805433600160a060020a039081169116146103df5760006000fd5b600160a060020a038216600090815260016020526040902054829060ff1615156104095760006000fd5b600160a060020a0383166000908152600160205260408120805460ff1916905591505b6002548210156105195782600160a060020a031660028381548110151561044f57fe5b906000526020600020900160005b9054906101000a9004600160a060020a0316600160a060020a0316141561050d5760028054600019810190811061049057fe5b906000526020600020900160005b9054906101000a9004600160a060020a03166002838154811015156104bf57fe5b906000526020600020900160005b6101000a815481600160a060020a030219169083600160a060020a0316021790555060016002818180549050039150816105079190610633565b50610519565b5b60019091019061042c565b604051600160a060020a0333811691908516907ff5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c90600090a35b5b505b5050565b600054600160a060020a031681565b60016020526000908152604090205460ff1681565b610586610687565b60028054806020026020016040519081016040528092919081815260200182805480156105dc57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116105be575b505050505090505b90565b60005433600160a060020a039081169116146106035760006000fd5b600160a060020a0381161561038d5760008054600160a060020a031916600160a060020a0383161790555b5b5b50565b81548183558181151161055357600083815260209020610553918101908301610699565b5b505050565b81548183558181151161055357600083815260209020610553918101908301610699565b5b505050565b60408051602081019091526000815290565b6105e491905b808211156106b3576000815560010161069f565b5090565b905600a165627a7a723058200355c2e534da7274d090b5a2209d5fbe4679ee2760a73c55c16e6d0ff1af016c0029",
"networks": {
"50": {
"links": {},
"events": {
"0x94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressAdded",
"type": "event"
},
"0xf5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressRemoved",
"type": "event"
}
},
"updated_at": 1502391794384,
"address": "0x168ead2eadb6b3b8f47d6ae0ff418451c1087239"
},
"42": {
"links": {},
"events": {
"0x94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressAdded",
"type": "event"
},
"0xf5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressRemoved",
"type": "event"
}
},
"updated_at": 1502391794384,
"address": "0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4"
},
"1": {
"links": {},
"events": {
"0x94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressAdded",
"type": "event"
},
"0xf5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressRemoved",
"type": "event"
}
},
"updated_at": 1502478966000,
"address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4"
}
},
"schema_version": "0.0.5",
"updated_at": 1502391794384
}

View File

@@ -0,0 +1,373 @@
{
"contract_name": "ZRXToken",
"abi": [
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"inputs": [],
"payable": false,
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
],
"unlinked_binary": "0x60606040526b033b2e3c9fd0803ce8000000600355341561001c57fe5b5b600354600160a060020a0333166000908152602081905260409020555b5b6106198061004a6000396000f3006060604052361561007d5763ffffffff60e060020a60003504166306fdde03811461007f578063095ea7b31461010f57806318160ddd1461014257806323b872dd14610164578063313ce5671461019d57806370a08231146101c357806395d89b41146101f1578063a9059cbb14610281578063dd62ed3e146102b4575bfe5b341561008757fe5b61008f6102e8565b6040805160208082528351818301528351919283929083019185019080838382156100d5575b8051825260208311156100d557601f1990920191602091820191016100b5565b505050905090810190601f1680156101015780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561011757fe5b61012e600160a060020a0360043516602435610316565b604080519115158252519081900360200190f35b341561014a57fe5b610152610381565b60408051918252519081900360200190f35b341561016c57fe5b61012e600160a060020a0360043581169060243516604435610387565b604080519115158252519081900360200190f35b34156101a557fe5b6101ad6104aa565b6040805160ff9092168252519081900360200190f35b34156101cb57fe5b610152600160a060020a03600435166104af565b60408051918252519081900360200190f35b34156101f957fe5b61008f6104ce565b6040805160208082528351818301528351919283929083019185019080838382156100d5575b8051825260208311156100d557601f1990920191602091820191016100b5565b505050905090810190601f1680156101015780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561028957fe5b61012e600160a060020a03600435166024356104ee565b604080519115158252519081900360200190f35b34156102bc57fe5b610152600160a060020a03600435811690602435166105a0565b60408051918252519081900360200190f35b6040805180820190915260118152607960020a70183c10283937ba37b1b7b6102a37b5b2b702602082015281565b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60035481565b600160a060020a03808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906103ca5750828110155b80156103f05750600160a060020a03841660009081526020819052604090205483810110155b1561049c57600160a060020a038085166000908152602081905260408082208054870190559187168152208054849003905560001981101561045a57600160a060020a03808616600090815260016020908152604080832033909416835292905220805484900390555b83600160a060020a031685600160a060020a03166000805160206105ce833981519152856040518082815260200191505060405180910390a3600191506104a1565b600091505b5b509392505050565b601281565b600160a060020a0381166000908152602081905260409020545b919050565b604080518082019091526003815260eb60020a620b4a4b02602082015281565b600160a060020a0333166000908152602081905260408120548290108015906105315750600160a060020a03831660009081526020819052604090205482810110155b1561059157600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191936000805160206105ce833981519152929081900390910190a350600161037b565b50600061037b565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a72305820c3b32a51a49bb8e7b08cb40d70b018590761113b8e17709dace660ef94b069e30029",
"networks": {
"50": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
},
"updated_at": 1502391794391
},
"42": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
},
"updated_at": 1502391794391,
"address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570"
},
"1": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
},
"updated_at": 1502477311000,
"address": "0xe41d2489571d322189246dafa5ebde1f4699f498"
}
},
"schema_version": "0.0.5",
"updated_at": 1502391794391
}

View File

@@ -0,0 +1,602 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.11;
import "./TokenTransferProxy.sol";
import "./base/Token.sol";
import "./base/SafeMath.sol";
/// @title Exchange - Facilitates exchange of ERC20 tokens.
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
contract Exchange is SafeMath {
// Error Codes
enum Errors {
ORDER_EXPIRED, // Order has already expired
ORDER_FULLY_FILLED_OR_CANCELLED, // Order has already been fully filled or cancelled
ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
INSUFFICIENT_BALANCE_OR_ALLOWANCE // Insufficient balance or allowance for token transfer
}
string constant public VERSION = "1.0.0";
uint16 constant public EXTERNAL_QUERY_GAS_LIMIT = 4999; // Changes to state require at least 5000 gas
address public ZRX_TOKEN_CONTRACT;
address public TOKEN_TRANSFER_PROXY_CONTRACT;
// Mappings of orderHash => amounts of takerTokenAmount filled or cancelled.
mapping (bytes32 => uint) public filled;
mapping (bytes32 => uint) public cancelled;
event LogFill(
address indexed maker,
address taker,
address indexed feeRecipient,
address makerToken,
address takerToken,
uint filledMakerTokenAmount,
uint filledTakerTokenAmount,
uint paidMakerFee,
uint paidTakerFee,
bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
bytes32 orderHash
);
event LogCancel(
address indexed maker,
address indexed feeRecipient,
address makerToken,
address takerToken,
uint cancelledMakerTokenAmount,
uint cancelledTakerTokenAmount,
bytes32 indexed tokens,
bytes32 orderHash
);
event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
struct Order {
address maker;
address taker;
address makerToken;
address takerToken;
address feeRecipient;
uint makerTokenAmount;
uint takerTokenAmount;
uint makerFee;
uint takerFee;
uint expirationTimestampInSec;
bytes32 orderHash;
}
function Exchange(address _zrxToken, address _tokenTransferProxy) {
ZRX_TOKEN_CONTRACT = _zrxToken;
TOKEN_TRANSFER_PROXY_CONTRACT = _tokenTransferProxy;
}
/*
* Core exchange functions
*/
/// @dev Fills the input order.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param fillTakerTokenAmount Desired amount of takerToken to fill.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfer will fail before attempting.
/// @param v ECDSA signature parameter v.
/// @param r ECDSA signature parameters r.
/// @param s ECDSA signature parameters s.
/// @return Total amount of takerToken filled in trade.
function fillOrder(
address[5] orderAddresses,
uint[6] orderValues,
uint fillTakerTokenAmount,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8 v,
bytes32 r,
bytes32 s)
public
returns (uint filledTakerTokenAmount)
{
Order memory order = Order({
maker: orderAddresses[0],
taker: orderAddresses[1],
makerToken: orderAddresses[2],
takerToken: orderAddresses[3],
feeRecipient: orderAddresses[4],
makerTokenAmount: orderValues[0],
takerTokenAmount: orderValues[1],
makerFee: orderValues[2],
takerFee: orderValues[3],
expirationTimestampInSec: orderValues[4],
orderHash: getOrderHash(orderAddresses, orderValues)
});
require(order.taker == address(0) || order.taker == msg.sender);
require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && fillTakerTokenAmount > 0);
require(isValidSignature(
order.maker,
order.orderHash,
v,
r,
s
));
if (block.timestamp >= order.expirationTimestampInSec) {
LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
return 0;
}
uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
filledTakerTokenAmount = min256(fillTakerTokenAmount, remainingTakerTokenAmount);
if (filledTakerTokenAmount == 0) {
LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
return 0;
}
if (isRoundingError(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount)) {
LogError(uint8(Errors.ROUNDING_ERROR_TOO_LARGE), order.orderHash);
return 0;
}
if (!shouldThrowOnInsufficientBalanceOrAllowance && !isTransferable(order, filledTakerTokenAmount)) {
LogError(uint8(Errors.INSUFFICIENT_BALANCE_OR_ALLOWANCE), order.orderHash);
return 0;
}
uint filledMakerTokenAmount = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
uint paidMakerFee;
uint paidTakerFee;
filled[order.orderHash] = safeAdd(filled[order.orderHash], filledTakerTokenAmount);
require(transferViaTokenTransferProxy(
order.makerToken,
order.maker,
msg.sender,
filledMakerTokenAmount
));
require(transferViaTokenTransferProxy(
order.takerToken,
msg.sender,
order.maker,
filledTakerTokenAmount
));
if (order.feeRecipient != address(0)) {
if (order.makerFee > 0) {
paidMakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerFee);
require(transferViaTokenTransferProxy(
ZRX_TOKEN_CONTRACT,
order.maker,
order.feeRecipient,
paidMakerFee
));
}
if (order.takerFee > 0) {
paidTakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.takerFee);
require(transferViaTokenTransferProxy(
ZRX_TOKEN_CONTRACT,
msg.sender,
order.feeRecipient,
paidTakerFee
));
}
}
LogFill(
order.maker,
msg.sender,
order.feeRecipient,
order.makerToken,
order.takerToken,
filledMakerTokenAmount,
filledTakerTokenAmount,
paidMakerFee,
paidTakerFee,
keccak256(order.makerToken, order.takerToken),
order.orderHash
);
return filledTakerTokenAmount;
}
/// @dev Cancels the input order.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param cancelTakerTokenAmount Desired amount of takerToken to cancel in order.
/// @return Amount of takerToken cancelled.
function cancelOrder(
address[5] orderAddresses,
uint[6] orderValues,
uint cancelTakerTokenAmount)
public
returns (uint)
{
Order memory order = Order({
maker: orderAddresses[0],
taker: orderAddresses[1],
makerToken: orderAddresses[2],
takerToken: orderAddresses[3],
feeRecipient: orderAddresses[4],
makerTokenAmount: orderValues[0],
takerTokenAmount: orderValues[1],
makerFee: orderValues[2],
takerFee: orderValues[3],
expirationTimestampInSec: orderValues[4],
orderHash: getOrderHash(orderAddresses, orderValues)
});
require(order.maker == msg.sender);
require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && cancelTakerTokenAmount > 0);
if (block.timestamp >= order.expirationTimestampInSec) {
LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
return 0;
}
uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
uint cancelledTakerTokenAmount = min256(cancelTakerTokenAmount, remainingTakerTokenAmount);
if (cancelledTakerTokenAmount == 0) {
LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
return 0;
}
cancelled[order.orderHash] = safeAdd(cancelled[order.orderHash], cancelledTakerTokenAmount);
LogCancel(
order.maker,
order.feeRecipient,
order.makerToken,
order.takerToken,
getPartialAmount(cancelledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount),
cancelledTakerTokenAmount,
keccak256(order.makerToken, order.takerToken),
order.orderHash
);
return cancelledTakerTokenAmount;
}
/*
* Wrapper functions
*/
/// @dev Fills an order with specified parameters and ECDSA signature, throws if specified amount not filled entirely.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param fillTakerTokenAmount Desired amount of takerToken to fill.
/// @param v ECDSA signature parameter v.
/// @param r ECDSA signature parameters r.
/// @param s ECDSA signature parameters s.
function fillOrKillOrder(
address[5] orderAddresses,
uint[6] orderValues,
uint fillTakerTokenAmount,
uint8 v,
bytes32 r,
bytes32 s)
public
{
require(fillOrder(
orderAddresses,
orderValues,
fillTakerTokenAmount,
false,
v,
r,
s
) == fillTakerTokenAmount);
}
/// @dev Synchronously executes multiple fill orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
function batchFillOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] fillTakerTokenAmounts,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
fillOrder(
orderAddresses[i],
orderValues[i],
fillTakerTokenAmounts[i],
shouldThrowOnInsufficientBalanceOrAllowance,
v[i],
r[i],
s[i]
);
}
}
/// @dev Synchronously executes multiple fillOrKill orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
function batchFillOrKillOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] fillTakerTokenAmounts,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
fillOrKillOrder(
orderAddresses[i],
orderValues[i],
fillTakerTokenAmounts[i],
v[i],
r[i],
s[i]
);
}
}
/// @dev Synchronously executes multiple fill orders in a single transaction until total fillTakerTokenAmount filled.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmount Desired total amount of takerToken to fill in orders.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
/// @return Total amount of fillTakerTokenAmount filled in orders.
function fillOrdersUpTo(
address[5][] orderAddresses,
uint[6][] orderValues,
uint fillTakerTokenAmount,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
returns (uint)
{
uint filledTakerTokenAmount = 0;
for (uint i = 0; i < orderAddresses.length; i++) {
require(orderAddresses[i][3] == orderAddresses[0][3]); // takerToken must be the same for each order
filledTakerTokenAmount = safeAdd(filledTakerTokenAmount, fillOrder(
orderAddresses[i],
orderValues[i],
safeSub(fillTakerTokenAmount, filledTakerTokenAmount),
shouldThrowOnInsufficientBalanceOrAllowance,
v[i],
r[i],
s[i]
));
if (filledTakerTokenAmount == fillTakerTokenAmount) break;
}
return filledTakerTokenAmount;
}
/// @dev Synchronously cancels multiple orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param cancelTakerTokenAmounts Array of desired amounts of takerToken to cancel in orders.
function batchCancelOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] cancelTakerTokenAmounts)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
cancelOrder(
orderAddresses[i],
orderValues[i],
cancelTakerTokenAmounts[i]
);
}
}
/*
* Constant public functions
*/
/// @dev Calculates Keccak-256 hash of order with specified parameters.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @return Keccak-256 hash of order.
function getOrderHash(address[5] orderAddresses, uint[6] orderValues)
public
constant
returns (bytes32)
{
return keccak256(
address(this),
orderAddresses[0], // maker
orderAddresses[1], // taker
orderAddresses[2], // makerToken
orderAddresses[3], // takerToken
orderAddresses[4], // feeRecipient
orderValues[0], // makerTokenAmount
orderValues[1], // takerTokenAmount
orderValues[2], // makerFee
orderValues[3], // takerFee
orderValues[4], // expirationTimestampInSec
orderValues[5] // salt
);
}
/// @dev Verifies that an order signature is valid.
/// @param signer address of signer.
/// @param hash Signed Keccak-256 hash.
/// @param v ECDSA signature parameter v.
/// @param r ECDSA signature parameters r.
/// @param s ECDSA signature parameters s.
/// @return Validity of order signature.
function isValidSignature(
address signer,
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s)
public
constant
returns (bool)
{
return signer == ecrecover(
keccak256("\x19Ethereum Signed Message:\n32", hash),
v,
r,
s
);
}
/// @dev Checks if rounding error > 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to multiply with numerator/denominator.
/// @return Rounding error is present.
function isRoundingError(uint numerator, uint denominator, uint target)
public
constant
returns (bool)
{
uint remainder = mulmod(target, numerator, denominator);
if (remainder == 0) return false; // No rounding error.
uint errPercentageTimes1000000 = safeDiv(
safeMul(remainder, 1000000),
safeMul(numerator, target)
);
return errPercentageTimes1000000 > 1000;
}
/// @dev Calculates partial value given a numerator and denominator.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function getPartialAmount(uint numerator, uint denominator, uint target)
public
constant
returns (uint)
{
return safeDiv(safeMul(numerator, target), denominator);
}
/// @dev Calculates the sum of values already filled and cancelled for a given order.
/// @param orderHash The Keccak-256 hash of the given order.
/// @return Sum of values already filled and cancelled.
function getUnavailableTakerTokenAmount(bytes32 orderHash)
public
constant
returns (uint)
{
return safeAdd(filled[orderHash], cancelled[orderHash]);
}
/*
* Internal functions
*/
/// @dev Transfers a token using TokenTransferProxy transferFrom function.
/// @param token Address of token to transferFrom.
/// @param from Address transfering token.
/// @param to Address receiving token.
/// @param value Amount of token to transfer.
/// @return Success of token transfer.
function transferViaTokenTransferProxy(
address token,
address from,
address to,
uint value)
internal
returns (bool)
{
return TokenTransferProxy(TOKEN_TRANSFER_PROXY_CONTRACT).transferFrom(token, from, to, value);
}
/// @dev Checks if any order transfers will fail.
/// @param order Order struct of params that will be checked.
/// @param fillTakerTokenAmount Desired amount of takerToken to fill.
/// @return Predicted result of transfers.
function isTransferable(Order order, uint fillTakerTokenAmount)
internal
constant // The called token contracts may attempt to change state, but will not be able to due to gas limits on getBalance and getAllowance.
returns (bool)
{
address taker = msg.sender;
uint fillMakerTokenAmount = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
if (order.feeRecipient != address(0)) {
bool isMakerTokenZRX = order.makerToken == ZRX_TOKEN_CONTRACT;
bool isTakerTokenZRX = order.takerToken == ZRX_TOKEN_CONTRACT;
uint paidMakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerFee);
uint paidTakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.takerFee);
uint requiredMakerZRX = isMakerTokenZRX ? safeAdd(fillMakerTokenAmount, paidMakerFee) : paidMakerFee;
uint requiredTakerZRX = isTakerTokenZRX ? safeAdd(fillTakerTokenAmount, paidTakerFee) : paidTakerFee;
if ( getBalance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
|| getAllowance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
|| getBalance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
|| getAllowance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
) return false;
if (!isMakerTokenZRX && ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount // Don't double check makerToken if ZRX
|| getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount)
) return false;
if (!isTakerTokenZRX && ( getBalance(order.takerToken, taker) < fillTakerTokenAmount // Don't double check takerToken if ZRX
|| getAllowance(order.takerToken, taker) < fillTakerTokenAmount)
) return false;
} else if ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount
|| getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount
|| getBalance(order.takerToken, taker) < fillTakerTokenAmount
|| getAllowance(order.takerToken, taker) < fillTakerTokenAmount
) return false;
return true;
}
/// @dev Get token balance of an address.
/// @param token Address of token.
/// @param owner Address of owner.
/// @return Token balance of owner.
function getBalance(address token, address owner)
internal
constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
returns (uint)
{
return Token(token).balanceOf.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner); // Limit gas to prevent reentrancy
}
/// @dev Get allowance of token given to TokenTransferProxy by an address.
/// @param token Address of token.
/// @param owner Address of owner.
/// @return Allowance of token given to TokenTransferProxy by owner.
function getAllowance(address token, address owner)
internal
constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
returns (uint)
{
return Token(token).allowance.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner, TOKEN_TRANSFER_PROXY_CONTRACT); // Limit gas to prevent reentrancy
}
}

View File

@@ -0,0 +1,132 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.11;
import "./base/MultiSigWallet.sol";
/// @title Multisignature wallet with time lock- Allows multiple parties to execute a transaction after a time lock has passed.
/// @author Amir Bandeali - <amir@0xProject.com>
contract MultiSigWalletWithTimeLock is MultiSigWallet {
event ConfirmationTimeSet(uint indexed transactionId, uint confirmationTime);
event TimeLockChange(uint secondsTimeLocked);
uint public secondsTimeLocked;
mapping (uint => uint) public confirmationTimes;
modifier notFullyConfirmed(uint transactionId) {
require(!isConfirmed(transactionId));
_;
}
modifier fullyConfirmed(uint transactionId) {
require(isConfirmed(transactionId));
_;
}
modifier pastTimeLock(uint transactionId) {
require(block.timestamp >= confirmationTimes[transactionId] + secondsTimeLocked);
_;
}
/*
* Public functions
*/
/// @dev Contract constructor sets initial owners, required number of confirmations, and time lock.
/// @param _owners List of initial owners.
/// @param _required Number of required confirmations.
/// @param _secondsTimeLocked Duration needed after a transaction is confirmed and before it becomes executable, in seconds.
function MultiSigWalletWithTimeLock(address[] _owners, uint _required, uint _secondsTimeLocked)
public
MultiSigWallet(_owners, _required)
{
secondsTimeLocked = _secondsTimeLocked;
}
/// @dev Changes the duration of the time lock for transactions.
/// @param _secondsTimeLocked Duration needed after a transaction is confirmed and before it becomes executable, in seconds.
function changeTimeLock(uint _secondsTimeLocked)
public
onlyWallet
{
secondsTimeLocked = _secondsTimeLocked;
TimeLockChange(_secondsTimeLocked);
}
/// @dev Allows an owner to confirm a transaction.
/// @param transactionId Transaction ID.
function confirmTransaction(uint transactionId)
public
ownerExists(msg.sender)
transactionExists(transactionId)
notConfirmed(transactionId, msg.sender)
notFullyConfirmed(transactionId)
{
confirmations[transactionId][msg.sender] = true;
Confirmation(msg.sender, transactionId);
if (isConfirmed(transactionId)) {
setConfirmationTime(transactionId, block.timestamp);
}
}
/// @dev Allows an owner to revoke a confirmation for a transaction.
/// @param transactionId Transaction ID.
function revokeConfirmation(uint transactionId)
public
ownerExists(msg.sender)
confirmed(transactionId, msg.sender)
notExecuted(transactionId)
notFullyConfirmed(transactionId)
{
confirmations[transactionId][msg.sender] = false;
Revocation(msg.sender, transactionId);
}
/// @dev Allows anyone to execute a confirmed transaction.
/// @param transactionId Transaction ID.
function executeTransaction(uint transactionId)
public
notExecuted(transactionId)
fullyConfirmed(transactionId)
pastTimeLock(transactionId)
{
Transaction storage tx = transactions[transactionId];
tx.executed = true;
if (tx.destination.call.value(tx.value)(tx.data))
Execution(transactionId);
else {
ExecutionFailure(transactionId);
tx.executed = false;
}
}
/*
* Internal functions
*/
/// @dev Sets the time of when a submission first passed.
function setConfirmationTime(uint transactionId, uint confirmationTime)
internal
{
confirmationTimes[transactionId] = confirmationTime;
ConfirmationTimeSet(transactionId, confirmationTime);
}
}

View File

@@ -0,0 +1,82 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.11;
import "./MultiSigWalletWithTimeLock.sol";
contract MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress is MultiSigWalletWithTimeLock {
address public TOKEN_TRANSFER_PROXY_CONTRACT;
modifier validRemoveAuthorizedAddressTx(uint transactionId) {
Transaction storage tx = transactions[transactionId];
require(tx.destination == TOKEN_TRANSFER_PROXY_CONTRACT);
require(isFunctionRemoveAuthorizedAddress(tx.data));
_;
}
/// @dev Contract constructor sets initial owners, required number of confirmations, time lock, and tokenTransferProxy address.
/// @param _owners List of initial owners.
/// @param _required Number of required confirmations.
/// @param _secondsTimeLocked Duration needed after a transaction is confirmed and before it becomes executable, in seconds.
/// @param _tokenTransferProxy Address of TokenTransferProxy contract.
function MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress(
address[] _owners,
uint _required,
uint _secondsTimeLocked,
address _tokenTransferProxy)
public
MultiSigWalletWithTimeLock(_owners, _required, _secondsTimeLocked)
{
TOKEN_TRANSFER_PROXY_CONTRACT = _tokenTransferProxy;
}
/// @dev Allows execution of removeAuthorizedAddress without time lock.
/// @param transactionId Transaction ID.
function executeRemoveAuthorizedAddress(uint transactionId)
public
notExecuted(transactionId)
fullyConfirmed(transactionId)
validRemoveAuthorizedAddressTx(transactionId)
{
Transaction storage tx = transactions[transactionId];
tx.executed = true;
if (tx.destination.call.value(tx.value)(tx.data))
Execution(transactionId);
else {
ExecutionFailure(transactionId);
tx.executed = false;
}
}
/// @dev Compares first 4 bytes of byte array to removeAuthorizedAddress function signature.
/// @param data Transaction data.
/// @return Successful if data is a call to removeAuthorizedAddress.
function isFunctionRemoveAuthorizedAddress(bytes data)
public
constant
returns (bool)
{
bytes4 removeAuthorizedAddressSignature = bytes4(sha3("removeAuthorizedAddress(address)"));
for (uint i = 0; i < 4; i++) {
require(data[i] == removeAuthorizedAddressSignature[i]);
}
return true;
}
}

View File

@@ -0,0 +1,308 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.11;
import "./base/Ownable.sol";
/// @title Token Registry - Stores metadata associated with ERC20 tokens. See ERC22 https://github.com/ethereum/EIPs/issues/22
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
contract TokenRegistry is Ownable {
event LogAddToken(
address indexed token,
string name,
string symbol,
uint8 decimals,
bytes ipfsHash,
bytes swarmHash
);
event LogRemoveToken(
address indexed token,
string name,
string symbol,
uint8 decimals,
bytes ipfsHash,
bytes swarmHash
);
event LogTokenNameChange(address indexed token, string oldName, string newName);
event LogTokenSymbolChange(address indexed token, string oldSymbol, string newSymbol);
event LogTokenIpfsHashChange(address indexed token, bytes oldIpfsHash, bytes newIpfsHash);
event LogTokenSwarmHashChange(address indexed token, bytes oldSwarmHash, bytes newSwarmHash);
mapping (address => TokenMetadata) public tokens;
mapping (string => address) tokenBySymbol;
mapping (string => address) tokenByName;
address[] public tokenAddresses;
struct TokenMetadata {
address token;
string name;
string symbol;
uint8 decimals;
bytes ipfsHash;
bytes swarmHash;
}
modifier tokenExists(address _token) {
require(tokens[_token].token != address(0));
_;
}
modifier tokenDoesNotExist(address _token) {
require(tokens[_token].token == address(0));
_;
}
modifier nameDoesNotExist(string _name) {
require(tokenByName[_name] == address(0));
_;
}
modifier symbolDoesNotExist(string _symbol) {
require(tokenBySymbol[_symbol] == address(0));
_;
}
modifier addressNotNull(address _address) {
require(_address != address(0));
_;
}
/// @dev Allows owner to add a new token to the registry.
/// @param _token Address of new token.
/// @param _name Name of new token.
/// @param _symbol Symbol for new token.
/// @param _decimals Number of decimals, divisibility of new token.
/// @param _ipfsHash IPFS hash of token icon.
/// @param _swarmHash Swarm hash of token icon.
function addToken(
address _token,
string _name,
string _symbol,
uint8 _decimals,
bytes _ipfsHash,
bytes _swarmHash)
public
onlyOwner
tokenDoesNotExist(_token)
addressNotNull(_token)
symbolDoesNotExist(_symbol)
nameDoesNotExist(_name)
{
tokens[_token] = TokenMetadata({
token: _token,
name: _name,
symbol: _symbol,
decimals: _decimals,
ipfsHash: _ipfsHash,
swarmHash: _swarmHash
});
tokenAddresses.push(_token);
tokenBySymbol[_symbol] = _token;
tokenByName[_name] = _token;
LogAddToken(
_token,
_name,
_symbol,
_decimals,
_ipfsHash,
_swarmHash
);
}
/// @dev Allows owner to remove an existing token from the registry.
/// @param _token Address of existing token.
function removeToken(address _token, uint _index)
public
onlyOwner
tokenExists(_token)
{
require(tokenAddresses[_index] == _token);
tokenAddresses[_index] = tokenAddresses[tokenAddresses.length - 1];
tokenAddresses.length -= 1;
TokenMetadata storage token = tokens[_token];
LogRemoveToken(
token.token,
token.name,
token.symbol,
token.decimals,
token.ipfsHash,
token.swarmHash
);
delete tokenBySymbol[token.symbol];
delete tokenByName[token.name];
delete tokens[_token];
}
/// @dev Allows owner to modify an existing token's name.
/// @param _token Address of existing token.
/// @param _name New name.
function setTokenName(address _token, string _name)
public
onlyOwner
tokenExists(_token)
nameDoesNotExist(_name)
{
TokenMetadata storage token = tokens[_token];
LogTokenNameChange(_token, token.name, _name);
delete tokenByName[token.name];
tokenByName[_name] = _token;
token.name = _name;
}
/// @dev Allows owner to modify an existing token's symbol.
/// @param _token Address of existing token.
/// @param _symbol New symbol.
function setTokenSymbol(address _token, string _symbol)
public
onlyOwner
tokenExists(_token)
symbolDoesNotExist(_symbol)
{
TokenMetadata storage token = tokens[_token];
LogTokenSymbolChange(_token, token.symbol, _symbol);
delete tokenBySymbol[token.symbol];
tokenBySymbol[_symbol] = _token;
token.symbol = _symbol;
}
/// @dev Allows owner to modify an existing token's IPFS hash.
/// @param _token Address of existing token.
/// @param _ipfsHash New IPFS hash.
function setTokenIpfsHash(address _token, bytes _ipfsHash)
public
onlyOwner
tokenExists(_token)
{
TokenMetadata storage token = tokens[_token];
LogTokenIpfsHashChange(_token, token.ipfsHash, _ipfsHash);
token.ipfsHash = _ipfsHash;
}
/// @dev Allows owner to modify an existing token's Swarm hash.
/// @param _token Address of existing token.
/// @param _swarmHash New Swarm hash.
function setTokenSwarmHash(address _token, bytes _swarmHash)
public
onlyOwner
tokenExists(_token)
{
TokenMetadata storage token = tokens[_token];
LogTokenSwarmHashChange(_token, token.swarmHash, _swarmHash);
token.swarmHash = _swarmHash;
}
/*
* Web3 call functions
*/
/// @dev Provides a registered token's address when given the token symbol.
/// @param _symbol Symbol of registered token.
/// @return Token's address.
function getTokenAddressBySymbol(string _symbol) constant returns (address) {
return tokenBySymbol[_symbol];
}
/// @dev Provides a registered token's address when given the token name.
/// @param _name Name of registered token.
/// @return Token's address.
function getTokenAddressByName(string _name) constant returns (address) {
return tokenByName[_name];
}
/// @dev Provides a registered token's metadata, looked up by address.
/// @param _token Address of registered token.
/// @return Token metadata.
function getTokenMetaData(address _token)
public
constant
returns (
address, //tokenAddress
string, //name
string, //symbol
uint8, //decimals
bytes, //ipfsHash
bytes //swarmHash
)
{
TokenMetadata memory token = tokens[_token];
return (
token.token,
token.name,
token.symbol,
token.decimals,
token.ipfsHash,
token.swarmHash
);
}
/// @dev Provides a registered token's metadata, looked up by name.
/// @param _name Name of registered token.
/// @return Token metadata.
function getTokenByName(string _name)
public
constant
returns (
address, //tokenAddress
string, //name
string, //symbol
uint8, //decimals
bytes, //ipfsHash
bytes //swarmHash
)
{
address _token = tokenByName[_name];
return getTokenMetaData(_token);
}
/// @dev Provides a registered token's metadata, looked up by symbol.
/// @param _symbol Symbol of registered token.
/// @return Token metadata.
function getTokenBySymbol(string _symbol)
public
constant
returns (
address, //tokenAddress
string, //name
string, //symbol
uint8, //decimals
bytes, //ipfsHash
bytes //swarmHash
)
{
address _token = tokenBySymbol[_symbol];
return getTokenMetaData(_token);
}
/// @dev Returns an array containing all token addresses.
/// @return Array of token addresses.
function getTokenAddresses()
public
constant
returns (address[])
{
return tokenAddresses;
}
}

View File

@@ -0,0 +1,115 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.11;
import "./base/Token.sol";
import "./base/Ownable.sol";
/// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance.
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
contract TokenTransferProxy is Ownable {
/// @dev Only authorized addresses can invoke functions with this modifier.
modifier onlyAuthorized {
require(authorized[msg.sender]);
_;
}
modifier targetAuthorized(address target) {
require(authorized[target]);
_;
}
modifier targetNotAuthorized(address target) {
require(!authorized[target]);
_;
}
mapping (address => bool) public authorized;
address[] public authorities;
event LogAuthorizedAddressAdded(address indexed target, address indexed caller);
event LogAuthorizedAddressRemoved(address indexed target, address indexed caller);
/*
* Public functions
*/
/// @dev Authorizes an address.
/// @param target Address to authorize.
function addAuthorizedAddress(address target)
public
onlyOwner
targetNotAuthorized(target)
{
authorized[target] = true;
authorities.push(target);
LogAuthorizedAddressAdded(target, msg.sender);
}
/// @dev Removes authorizion of an address.
/// @param target Address to remove authorization from.
function removeAuthorizedAddress(address target)
public
onlyOwner
targetAuthorized(target)
{
delete authorized[target];
for (uint i = 0; i < authorities.length; i++) {
if (authorities[i] == target) {
authorities[i] = authorities[authorities.length - 1];
authorities.length -= 1;
break;
}
}
LogAuthorizedAddressRemoved(target, msg.sender);
}
/// @dev Calls into ERC20 Token contract, invoking transferFrom.
/// @param token Address of token to transfer.
/// @param from Address to transfer token from.
/// @param to Address to transfer token to.
/// @param value Amount of token to transfer.
/// @return Success of transfer.
function transferFrom(
address token,
address from,
address to,
uint value)
public
onlyAuthorized
returns (bool)
{
return Token(token).transferFrom(from, to, value);
}
/*
* Public constant functions
*/
/// @dev Gets all authorized addresses.
/// @return Array of authorized addresses.
function getAuthorizedAddresses()
public
constant
returns (address[])
{
return authorities;
}
}

View File

@@ -0,0 +1,365 @@
pragma solidity 0.4.11;
/// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution.
/// @author Stefan George - <stefan.george@consensys.net>
contract MultiSigWallet {
uint constant public MAX_OWNER_COUNT = 50;
event Confirmation(address indexed sender, uint indexed transactionId);
event Revocation(address indexed sender, uint indexed transactionId);
event Submission(uint indexed transactionId);
event Execution(uint indexed transactionId);
event ExecutionFailure(uint indexed transactionId);
event Deposit(address indexed sender, uint value);
event OwnerAddition(address indexed owner);
event OwnerRemoval(address indexed owner);
event RequirementChange(uint required);
mapping (uint => Transaction) public transactions;
mapping (uint => mapping (address => bool)) public confirmations;
mapping (address => bool) public isOwner;
address[] public owners;
uint public required;
uint public transactionCount;
struct Transaction {
address destination;
uint value;
bytes data;
bool executed;
}
modifier onlyWallet() {
if (msg.sender != address(this))
throw;
_;
}
modifier ownerDoesNotExist(address owner) {
if (isOwner[owner])
throw;
_;
}
modifier ownerExists(address owner) {
if (!isOwner[owner])
throw;
_;
}
modifier transactionExists(uint transactionId) {
if (transactions[transactionId].destination == 0)
throw;
_;
}
modifier confirmed(uint transactionId, address owner) {
if (!confirmations[transactionId][owner])
throw;
_;
}
modifier notConfirmed(uint transactionId, address owner) {
if (confirmations[transactionId][owner])
throw;
_;
}
modifier notExecuted(uint transactionId) {
if (transactions[transactionId].executed)
throw;
_;
}
modifier notNull(address _address) {
if (_address == 0)
throw;
_;
}
modifier validRequirement(uint ownerCount, uint _required) {
if ( ownerCount > MAX_OWNER_COUNT
|| _required > ownerCount
|| _required == 0
|| ownerCount == 0)
throw;
_;
}
/// @dev Fallback function allows to deposit ether.
function()
payable
{
if (msg.value > 0)
Deposit(msg.sender, msg.value);
}
/*
* Public functions
*/
/// @dev Contract constructor sets initial owners and required number of confirmations.
/// @param _owners List of initial owners.
/// @param _required Number of required confirmations.
function MultiSigWallet(address[] _owners, uint _required)
public
validRequirement(_owners.length, _required)
{
for (uint i=0; i<_owners.length; i++) {
if (isOwner[_owners[i]] || _owners[i] == 0)
throw;
isOwner[_owners[i]] = true;
}
owners = _owners;
required = _required;
}
/// @dev Allows to add a new owner. Transaction has to be sent by wallet.
/// @param owner Address of new owner.
function addOwner(address owner)
public
onlyWallet
ownerDoesNotExist(owner)
notNull(owner)
validRequirement(owners.length + 1, required)
{
isOwner[owner] = true;
owners.push(owner);
OwnerAddition(owner);
}
/// @dev Allows to remove an owner. Transaction has to be sent by wallet.
/// @param owner Address of owner.
function removeOwner(address owner)
public
onlyWallet
ownerExists(owner)
{
isOwner[owner] = false;
for (uint i=0; i<owners.length - 1; i++)
if (owners[i] == owner) {
owners[i] = owners[owners.length - 1];
break;
}
owners.length -= 1;
if (required > owners.length)
changeRequirement(owners.length);
OwnerRemoval(owner);
}
/// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet.
/// @param owner Address of owner to be replaced.
/// @param owner Address of new owner.
function replaceOwner(address owner, address newOwner)
public
onlyWallet
ownerExists(owner)
ownerDoesNotExist(newOwner)
{
for (uint i=0; i<owners.length; i++)
if (owners[i] == owner) {
owners[i] = newOwner;
break;
}
isOwner[owner] = false;
isOwner[newOwner] = true;
OwnerRemoval(owner);
OwnerAddition(newOwner);
}
/// @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet.
/// @param _required Number of required confirmations.
function changeRequirement(uint _required)
public
onlyWallet
validRequirement(owners.length, _required)
{
required = _required;
RequirementChange(_required);
}
/// @dev Allows an owner to submit and confirm a transaction.
/// @param destination Transaction target address.
/// @param value Transaction ether value.
/// @param data Transaction data payload.
/// @return Returns transaction ID.
function submitTransaction(address destination, uint value, bytes data)
public
returns (uint transactionId)
{
transactionId = addTransaction(destination, value, data);
confirmTransaction(transactionId);
}
/// @dev Allows an owner to confirm a transaction.
/// @param transactionId Transaction ID.
function confirmTransaction(uint transactionId)
public
ownerExists(msg.sender)
transactionExists(transactionId)
notConfirmed(transactionId, msg.sender)
{
confirmations[transactionId][msg.sender] = true;
Confirmation(msg.sender, transactionId);
executeTransaction(transactionId);
}
/// @dev Allows an owner to revoke a confirmation for a transaction.
/// @param transactionId Transaction ID.
function revokeConfirmation(uint transactionId)
public
ownerExists(msg.sender)
confirmed(transactionId, msg.sender)
notExecuted(transactionId)
{
confirmations[transactionId][msg.sender] = false;
Revocation(msg.sender, transactionId);
}
/// @dev Allows anyone to execute a confirmed transaction.
/// @param transactionId Transaction ID.
function executeTransaction(uint transactionId)
public
notExecuted(transactionId)
{
if (isConfirmed(transactionId)) {
Transaction tx = transactions[transactionId];
tx.executed = true;
if (tx.destination.call.value(tx.value)(tx.data))
Execution(transactionId);
else {
ExecutionFailure(transactionId);
tx.executed = false;
}
}
}
/// @dev Returns the confirmation status of a transaction.
/// @param transactionId Transaction ID.
/// @return Confirmation status.
function isConfirmed(uint transactionId)
public
constant
returns (bool)
{
uint count = 0;
for (uint i=0; i<owners.length; i++) {
if (confirmations[transactionId][owners[i]])
count += 1;
if (count == required)
return true;
}
}
/*
* Internal functions
*/
/// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet.
/// @param destination Transaction target address.
/// @param value Transaction ether value.
/// @param data Transaction data payload.
/// @return Returns transaction ID.
function addTransaction(address destination, uint value, bytes data)
internal
notNull(destination)
returns (uint transactionId)
{
transactionId = transactionCount;
transactions[transactionId] = Transaction({
destination: destination,
value: value,
data: data,
executed: false
});
transactionCount += 1;
Submission(transactionId);
}
/*
* Web3 call functions
*/
/// @dev Returns number of confirmations of a transaction.
/// @param transactionId Transaction ID.
/// @return Number of confirmations.
function getConfirmationCount(uint transactionId)
public
constant
returns (uint count)
{
for (uint i=0; i<owners.length; i++)
if (confirmations[transactionId][owners[i]])
count += 1;
}
/// @dev Returns total number of transactions after filers are applied.
/// @param pending Include pending transactions.
/// @param executed Include executed transactions.
/// @return Total number of transactions after filters are applied.
function getTransactionCount(bool pending, bool executed)
public
constant
returns (uint count)
{
for (uint i=0; i<transactionCount; i++)
if ( pending && !transactions[i].executed
|| executed && transactions[i].executed)
count += 1;
}
/// @dev Returns list of owners.
/// @return List of owner addresses.
function getOwners()
public
constant
returns (address[])
{
return owners;
}
/// @dev Returns array with owner addresses, which confirmed transaction.
/// @param transactionId Transaction ID.
/// @return Returns array of owner addresses.
function getConfirmations(uint transactionId)
public
constant
returns (address[] _confirmations)
{
address[] memory confirmationsTemp = new address[](owners.length);
uint count = 0;
uint i;
for (i=0; i<owners.length; i++)
if (confirmations[transactionId][owners[i]]) {
confirmationsTemp[count] = owners[i];
count += 1;
}
_confirmations = new address[](count);
for (i=0; i<count; i++)
_confirmations[i] = confirmationsTemp[i];
}
/// @dev Returns list of transaction IDs in defined range.
/// @param from Index start position of transaction array.
/// @param to Index end position of transaction array.
/// @param pending Include pending transactions.
/// @param executed Include executed transactions.
/// @return Returns array of transaction IDs.
function getTransactionIds(uint from, uint to, bool pending, bool executed)
public
constant
returns (uint[] _transactionIds)
{
uint[] memory transactionIdsTemp = new uint[](transactionCount);
uint count = 0;
uint i;
for (i=0; i<transactionCount; i++)
if ( pending && !transactions[i].executed
|| executed && transactions[i].executed)
{
transactionIdsTemp[count] = i;
count += 1;
}
_transactionIds = new uint[](to - from);
for (i=from; i<to; i++)
_transactionIds[i - from] = transactionIdsTemp[i];
}
}

View File

@@ -0,0 +1,27 @@
pragma solidity 0.4.11;
/*
* Ownable
*
* Base contract with an owner.
* Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
*/
contract Ownable {
address public owner;
function Ownable() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}

View File

@@ -0,0 +1,41 @@
pragma solidity 0.4.11;
contract SafeMath {
function safeMul(uint a, uint b) internal constant returns (uint256) {
uint c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function safeDiv(uint a, uint b) internal constant returns (uint256) {
uint c = a / b;
return c;
}
function safeSub(uint a, uint b) internal constant returns (uint256) {
assert(b <= a);
return a - b;
}
function safeAdd(uint a, uint b) internal constant returns (uint256) {
uint c = a + b;
assert(c >= a);
return c;
}
function max64(uint64 a, uint64 b) internal constant returns (uint64) {
return a >= b ? a : b;
}
function min64(uint64 a, uint64 b) internal constant returns (uint64) {
return a < b ? a : b;
}
function max256(uint256 a, uint256 b) internal constant returns (uint256) {
return a >= b ? a : b;
}
function min256(uint256 a, uint256 b) internal constant returns (uint256) {
return a < b ? a : b;
}
}

View File

@@ -0,0 +1,44 @@
pragma solidity 0.4.11;
import "./Token.sol";
contract StandardToken is Token {
function transfer(address _to, uint _value) returns (bool) {
//Default assumes totalSupply can't be over max (2^256 - 1).
if (balances[msg.sender] >= _value && balances[_to] + _value >= balances[_to]) {
balances[msg.sender] -= _value;
balances[_to] += _value;
Transfer(msg.sender, _to, _value);
return true;
} else { return false; }
}
function transferFrom(address _from, address _to, uint _value) returns (bool) {
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value >= balances[_to]) {
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
Transfer(_from, _to, _value);
return true;
} else { return false; }
}
function balanceOf(address _owner) constant returns (uint) {
return balances[_owner];
}
function approve(address _spender, uint _value) returns (bool) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) constant returns (uint) {
return allowed[_owner][_spender];
}
mapping (address => uint) balances;
mapping (address => mapping (address => uint)) allowed;
uint public totalSupply;
}

View File

@@ -0,0 +1,38 @@
pragma solidity 0.4.11;
contract Token {
/// @return total amount of tokens
function totalSupply() constant returns (uint supply) {}
/// @param _owner The address from which the balance will be retrieved
/// @return The balance
function balanceOf(address _owner) constant returns (uint balance) {}
/// @notice send `_value` token to `_to` from `msg.sender`
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transfer(address _to, uint _value) returns (bool success) {}
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transferFrom(address _from, address _to, uint _value) returns (bool success) {}
/// @notice `msg.sender` approves `_addr` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of wei to be approved for transfer
/// @return Whether the approval was successful or not
function approve(address _spender, uint _value) returns (bool success) {}
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return Amount of remaining tokens allowed to spent
function allowance(address _owner, address _spender) constant returns (uint remaining) {}
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}

View File

@@ -0,0 +1,33 @@
pragma solidity 0.4.11;
import "./Mintable.sol";
import "./../base/Ownable.sol";
contract DummyToken is Mintable, Ownable {
string public name;
string public symbol;
uint public decimals;
function DummyToken(
string _name,
string _symbol,
uint _decimals,
uint _totalSupply)
{
name = _name;
symbol = _symbol;
decimals = _decimals;
totalSupply = _totalSupply;
balances[msg.sender] = _totalSupply;
}
function setBalance(address _target, uint _value) onlyOwner {
uint currBalance = balanceOf(_target);
if (_value < currBalance) {
totalSupply = safeSub(totalSupply, safeSub(currBalance, _value));
} else {
totalSupply = safeAdd(totalSupply, safeSub(_value, currBalance));
}
balances[_target] = _value;
}
}

View File

@@ -0,0 +1,29 @@
pragma solidity 0.4.11;
import "./../base/StandardToken.sol";
contract MaliciousToken is StandardToken {
uint8 stateToUpdate = 1; // Not null so that change only requires 5000 gas
function updateState() internal {
stateToUpdate++;
}
function balanceOf(address _owner)
public
constant
returns (uint)
{
updateState();
return balances[_owner];
}
function allowance(address _owner, address _spender)
public
constant
returns (uint)
{
updateState();
return allowed[_owner][_spender];
}
}

View File

@@ -0,0 +1,16 @@
pragma solidity 0.4.11;
import "./../tokens/UnlimitedAllowanceToken.sol";
import "./../base/SafeMath.sol";
/*
* Mintable
* Base contract that creates a mintable UnlimitedAllowanceToken
*/
contract Mintable is UnlimitedAllowanceToken, SafeMath {
function mint(uint _value) {
require(_value <= 100000000000000000000);
balances[msg.sender] = safeAdd(_value, balances[msg.sender]);
totalSupply = safeAdd(totalSupply, _value);
}
}

View File

@@ -0,0 +1,56 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.11;
import "./UnlimitedAllowanceToken.sol";
import "./../base/SafeMath.sol";
contract EtherToken is UnlimitedAllowanceToken, SafeMath {
string constant public name = "Ether Token";
string constant public symbol = "WETH";
uint8 constant public decimals = 18;
/// @dev Fallback to calling deposit when ether is sent directly to contract.
function()
public
payable
{
deposit();
}
/// @dev Buys tokens with Ether, exchanging them 1:1.
function deposit()
public
payable
{
balances[msg.sender] = safeAdd(balances[msg.sender], msg.value);
totalSupply = safeAdd(totalSupply, msg.value);
}
/// @dev Sells tokens in exchange for Ether, exchanging them 1:1.
/// @param amount Number of tokens to sell.
function withdraw(uint amount)
public
{
balances[msg.sender] = safeSub(balances[msg.sender], amount);
totalSupply = safeSub(totalSupply, amount);
require(msg.sender.send(amount));
}
}

View File

@@ -0,0 +1,52 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.11;
import "./../base/StandardToken.sol";
contract UnlimitedAllowanceToken is StandardToken {
uint constant MAX_UINT = 2**256 - 1;
/// @dev ERC20 transferFrom, modified such that an allowance of MAX_UINT represents an unlimited allowance.
/// @param _from Address to transfer from.
/// @param _to Address to transfer to.
/// @param _value Amount to transfer.
/// @return Success of transfer.
function transferFrom(address _from, address _to, uint _value)
public
returns (bool)
{
uint allowance = allowed[_from][msg.sender];
if (balances[_from] >= _value
&& allowance >= _value
&& balances[_to] + _value >= balances[_to]
) {
balances[_to] += _value;
balances[_from] -= _value;
if (allowance < MAX_UINT) {
allowed[_from][msg.sender] -= _value;
}
Transfer(_from, _to, _value);
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,33 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.11;
import "./UnlimitedAllowanceToken.sol";
contract ZRXToken is UnlimitedAllowanceToken {
uint8 constant public decimals = 18;
uint public totalSupply = 10**27; // 1 billion tokens, 18 decimal places
string constant public name = "0x Protocol Token";
string constant public symbol = "ZRX";
function ZRXToken() {
balances[msg.sender] = totalSupply;
}
}

View File

@@ -0,0 +1,157 @@
import * as _ from 'lodash';
import * as path from 'path';
import * as yargs from 'yargs';
import {commands} from './src/commands';
import {network} from './src/utils/network';
import {
CliOptions,
CompilerOptions,
DeployerOptions,
} from './src/utils/types';
const DEFAULT_OPTIMIZER_ENABLED = false;
const DEFAULT_CONTRACTS_DIR = path.resolve('contracts');
const DEFAULT_ARTIFACTS_DIR = `${path.resolve('build')}/artifacts/`;
const DEFAULT_NETWORK_ID = 50;
const DEFAULT_JSONRPC_PORT = 8545;
const DEFAULT_GAS_PRICE = ((10 ** 9) * 2).toString();
/**
* Compiles all contracts with options passed in through CLI.
* @param argv Instance of process.argv provided by yargs.
*/
async function onCompileCommand(args: CliOptions): Promise<void> {
const opts: CompilerOptions = {
contractsDir: args.contractsDir,
networkId: args.networkId,
optimizerEnabled: args.shouldOptimize ? 1 : 0,
artifactsDir: args.artifactsDir,
};
await commands.compileAsync(opts);
}
/**
* Compiles all contracts and runs migration script with options passed in through CLI.
* Uses network ID of running node.
* @param argv Instance of process.argv provided by yargs.
*/
async function onMigrateCommand(argv: CliOptions): Promise<void> {
const networkIdIfExists = await network.getNetworkIdIfExistsAsync(argv.jsonrpcPort);
const compilerOpts: CompilerOptions = {
contractsDir: argv.contractsDir,
networkId: networkIdIfExists,
optimizerEnabled: argv.shouldOptimize ? 1 : 0,
artifactsDir: argv.artifactsDir,
};
await commands.compileAsync(compilerOpts);
const defaults = {
gasPrice: argv.gasPrice,
from: argv.account,
};
const deployerOpts: DeployerOptions = {
artifactsDir: argv.artifactsDir,
jsonrpcPort: argv.jsonrpcPort,
networkId: networkIdIfExists,
defaults,
};
await commands.migrateAsync(deployerOpts);
}
/**
* Deploys a single contract with provided name and args.
* @param argv Instance of process.argv provided by yargs.
*/
async function onDeployCommand(argv: CliOptions): Promise<void> {
const networkIdIfExists = await network.getNetworkIdIfExistsAsync(argv.jsonrpcPort);
const compilerOpts: CompilerOptions = {
contractsDir: argv.contractsDir,
networkId: networkIdIfExists,
optimizerEnabled: argv.shouldOptimize ? 1 : 0,
artifactsDir: argv.artifactsDir,
};
await commands.compileAsync(compilerOpts);
const defaults = {
gasPrice: argv.gasPrice,
from: argv.account,
};
const deployerOpts: DeployerOptions = {
artifactsDir: argv.artifactsDir,
jsonrpcPort: argv.jsonrpcPort,
networkId: networkIdIfExists,
defaults,
};
const deployerArgsString = argv.args;
const deployerArgs = deployerArgsString.split(',');
await commands.deployAsync(argv.contract, deployerArgs, deployerOpts);
}
/**
* Provides extra required options for deploy command.
* @param yargsInstance yargs instance provided in builder function callback.
*/
function deployCommandBuilder(yargsInstance: any) {
return yargsInstance
.option('contract', {
type: 'string',
description: 'name of contract to deploy, exluding .sol extension',
})
.option('args', {
type: 'string',
description: 'comma separated list of constructor args to deploy contract with',
})
.demandOption(['contract', 'args'])
.help()
.argv;
}
(() => {
return yargs
.option('contracts-dir', {
type: 'string',
default: DEFAULT_CONTRACTS_DIR,
description: 'path of contracts directory to compile',
})
.option('network-id', {
type: 'number',
default: DEFAULT_NETWORK_ID,
description: 'mainnet=1, kovan=42, testrpc=50',
})
.option('should-optimize', {
type: 'boolean',
default: DEFAULT_OPTIMIZER_ENABLED,
description: 'enable optimizer',
})
.option('artifacts-dir', {
type: 'string',
default: DEFAULT_ARTIFACTS_DIR,
description: 'path to write contracts artifacts to',
})
.option('jsonrpc-port', {
type: 'number',
default: DEFAULT_JSONRPC_PORT,
description: 'port connected to JSON RPC',
})
.option('gas-price', {
type: 'string',
default: DEFAULT_GAS_PRICE,
description: 'gasPrice to be used for transactions',
})
.option('account', {
type: 'string',
description: 'account to use for deploying contracts',
})
.command('compile',
'compile contracts',
_.noop,
onCompileCommand)
.command('migrate',
'compile and deploy contracts using migration scripts',
_.noop,
onMigrateCommand)
.command('deploy',
'deploy a single contract with provided arguments',
deployCommandBuilder,
onDeployCommand)
.help()
.argv;
})();

View File

@@ -0,0 +1,40 @@
import {constants} from './../../src/utils/constants';
import {Token} from './../../src/utils/types';
export const tokenInfo: Token[] = [
{
name: 'Augur Reputation Token',
symbol: 'REP',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
name: 'Digix DAO Token',
symbol: 'DGD',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
name: 'Golem Network Token',
symbol: 'GNT',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
name: 'MakerDAO',
symbol: 'MKR',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
name: 'Melon Token',
symbol: 'MLN',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
];

View File

@@ -0,0 +1,106 @@
import {BigNumber} from 'bignumber.js';
import * as _ from 'lodash';
import * as Web3 from 'web3';
import {Deployer} from './../src/deployer';
import {constants} from './../src/utils/constants';
import {Token} from './../src/utils/types';
import {Web3Wrapper} from './../src/utils/web3_wrapper';
import {tokenInfo} from './config/token_info';
export const migrator = {
/**
* Custom migrations should be defined in this function. This will be called with the CLI 'migrate' command.
* @param deployer Deployer instance.
*/
async runMigrationsAsync(deployer: Deployer): Promise<void> {
const web3Wrapper: Web3Wrapper = deployer.web3Wrapper;
const accounts: string[] = await web3Wrapper.getAvailableAddressesAsync();
const independentContracts: Web3.ContractInstance[] = await Promise.all([
deployer.deployAndSaveAsync('TokenTransferProxy'),
deployer.deployAndSaveAsync('ZRXToken'),
deployer.deployAndSaveAsync('EtherToken'),
deployer.deployAndSaveAsync('TokenRegistry'),
]);
const [tokenTransferProxy, zrxToken, etherToken, tokenReg] = independentContracts;
const exchangeArgs = [zrxToken.address, tokenTransferProxy.address];
const owners = [accounts[0], accounts[1]];
const confirmationsRequired = new BigNumber(2);
const secondsRequired = new BigNumber(0);
const multiSigArgs = [owners, confirmationsRequired, secondsRequired, tokenTransferProxy.address];
const dependentContracts: Web3.ContractInstance[] = await Promise.all([
deployer.deployAndSaveAsync('Exchange', exchangeArgs),
deployer.deployAndSaveAsync('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', multiSigArgs),
]);
const [exchange, multiSig] = dependentContracts;
const owner = accounts[0];
await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {from: owner});
await tokenTransferProxy.transferOwnership.sendTransactionAsync(multiSig.address, {from: owner});
const tokensToRegister: Web3.ContractInstance[] = await Promise.all(
_.map(tokenInfo, async (token: Token): Promise<Web3.ContractInstance> => {
const totalSupply = new BigNumber(0);
const args = [
token.name,
token.symbol,
token.decimals,
totalSupply,
];
return deployer.deployAsync('DummyToken', args);
}),
);
const addTokenGasEstimate = await tokenReg.addToken.estimateGasAsync(
tokensToRegister[0].address,
tokenInfo[0].name,
tokenInfo[0].symbol,
tokenInfo[0].decimals,
tokenInfo[0].ipfsHash,
tokenInfo[0].swarmHash,
{from: owner},
);
const addTokenPromises = [
tokenReg.addToken.sendTransactionAsync(
zrxToken.address,
'0x Protocol Token',
'ZRX',
18,
constants.NULL_BYTES,
constants.NULL_BYTES,
{
from: owner,
gas: addTokenGasEstimate,
},
),
tokenReg.addToken.sendTransactionAsync(
etherToken.address,
'Ether Token',
'WETH',
18,
constants.NULL_BYTES,
constants.NULL_BYTES,
{
from: owner,
gas: addTokenGasEstimate,
},
),
];
const addDummyTokenPromises = _.map(tokenInfo, async (token: Token, i: number): Promise<void> => {
return tokenReg.addToken.sendTransactionAsync(
tokensToRegister[i].address,
token.name,
token.symbol,
token.decimals,
token.ipfsHash,
token.swarmHash,
{
from: owner,
gas: addTokenGasEstimate,
},
);
});
await Promise.all([...addDummyTokenPromises, ...addTokenPromises]);
},
};

View File

@@ -0,0 +1,15 @@
interface BinaryPaths {
[key: string]: string;
}
export const binPaths: BinaryPaths = {
'0.4.10': 'soljson-v0.4.10+commit.f0d539ae.js',
'0.4.11': 'soljson-v0.4.11+commit.68ef5810.js',
'0.4.12': 'soljson-v0.4.12+commit.194ff033.js',
'0.4.13': 'soljson-v0.4.13+commit.fb4cb1a.js',
'0.4.14': 'soljson-v0.4.14+commit.c2215d46.js',
'0.4.15': 'soljson-v0.4.15+commit.bbb8e64f.js',
'0.4.16': 'soljson-v0.4.16+commit.d7661dd9.js',
'0.4.17': 'soljson-v0.4.17+commit.bdeb9e52.js',
'0.4.18': 'soljson-v0.4.18+commit.9cf6e910.js',
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,19 @@
import {migrator} from './../migrations/migrate';
import {Compiler} from './compiler';
import {Deployer} from './deployer';
import {CompilerOptions, DeployerOptions} from './utils/types';
export const commands = {
async compileAsync(opts: CompilerOptions): Promise<void> {
const compiler = new Compiler(opts);
await compiler.compileAllAsync();
},
async migrateAsync(opts: DeployerOptions): Promise<void> {
const deployer = new Deployer(opts);
await migrator.runMigrationsAsync(deployer);
},
async deployAsync(contractName: string, args: any[], opts: DeployerOptions): Promise<void> {
const deployer = new Deployer(opts);
await deployer.deployAndSaveAsync(contractName, args);
},
};

View File

@@ -0,0 +1,248 @@
import promisify = require('es6-promisify');
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import * as path from 'path';
import solc = require('solc');
import * as Web3 from 'web3';
import {binPaths} from './../solc/bin_paths';
import {fsWrapper} from './utils/fs_wrapper';
import {
CompilerOptions,
ContractArtifact,
ContractData,
ContractNetworks,
ContractSources,
ImportContents,
SolcErrors,
} from './utils/types';
import {utils} from './utils/utils';
const SOLIDITY_FILE_EXTENSION = '.sol';
/**
* Recursively retrieves Solidity source code from directory.
* @param dirPath Directory to search.
* @return Mapping of contract name to contract source.
*/
async function getContractSourcesAsync(dirPath: string): Promise<ContractSources> {
let dirContents: string[] = [];
try {
dirContents = await fsWrapper.readdirAsync(dirPath);
} catch (err) {
throw new Error(`No directory found at ${dirPath}`);
}
let sources: ContractSources = {};
for (const name of dirContents) {
const contentPath = `${dirPath}/${name}`;
if (path.extname(name) === SOLIDITY_FILE_EXTENSION) {
try {
const opts = {
encoding: 'utf8',
};
sources[name] = await fsWrapper.readFileAsync(contentPath, opts);
utils.consoleLog(`Reading ${name} source...`);
} catch (err) {
utils.consoleLog(`Could not find file at ${contentPath}`);
}
} else {
try {
const nestedSources = await getContractSourcesAsync(contentPath);
sources = {
...sources,
...nestedSources,
};
} catch (err) {
utils.consoleLog(`${contentPath} is not a directory or ${SOLIDITY_FILE_EXTENSION} file`);
}
}
}
return sources;
}
/**
* Searches Solidity source code for compiler version.
* @param source Source code of contract.
* @return Solc compiler version.
*/
function parseSolidityVersion(source: string): string {
const solcVersionMatch = source.match(/(?:solidity\s\^?)([0-9]{1,2}[.][0-9]{1,2}[.][0-9]{1,2})/);
if (_.isNull(solcVersionMatch)) {
throw new Error('Could not find Solidity version in source');
}
const solcVersion = solcVersionMatch[1];
return solcVersion;
}
/**
* Normalizes the path found in the error message.
* Example: converts 'base/Token.sol:6:46: Warning: Unused local variable'
* to 'Token.sol:6:46: Warning: Unused local variable'
* This is used to prevent logging the same error multiple times.
* @param errMsg An error message from the compiled output.
* @return The error message with directories truncated from the contract path.
*/
function getNormalizedErrMsg(errMsg: string): string {
const errPathMatch = errMsg.match(/(.*\.sol)/);
if (_.isNull(errPathMatch)) {
throw new Error('Could not find a path in error message');
}
const errPath = errPathMatch[0];
const baseContract = path.basename(errPath);
const normalizedErrMsg = errMsg.replace(errPath, baseContract);
return normalizedErrMsg;
}
export class Compiler {
private contractsDir: string;
private networkId: number;
private optimizerEnabled: number;
private artifactsDir: string;
private contractSourcesIfExists?: ContractSources;
private solcErrors: Set<string>;
constructor(opts: CompilerOptions) {
this.contractsDir = opts.contractsDir;
this.networkId = opts.networkId;
this.optimizerEnabled = opts.optimizerEnabled;
this.artifactsDir = opts.artifactsDir;
this.solcErrors = new Set();
}
/**
* Compiles all Solidity files found in contractsDir and writes JSON artifacts to artifactsDir.
*/
public async compileAllAsync(): Promise<void> {
await this.createArtifactsDirIfDoesNotExistAsync();
this.contractSourcesIfExists = await getContractSourcesAsync(this.contractsDir);
const contractBaseNames = _.keys(this.contractSourcesIfExists);
const compiledContractPromises = _.map(contractBaseNames, async (contractBaseName: string): Promise<void> => {
return this.compileContractAsync(contractBaseName);
});
await Promise.all(compiledContractPromises);
this.solcErrors.forEach(errMsg => {
utils.consoleLog(errMsg);
});
}
/**
* Compiles contract and saves artifact to artifactsDir.
* @param contractBaseName Name of contract with '.sol' extension.
*/
private async compileContractAsync(contractBaseName: string): Promise<void> {
if (_.isUndefined(this.contractSourcesIfExists)) {
throw new Error('Contract sources not yet initialized');
}
const source = this.contractSourcesIfExists[contractBaseName];
const contractName = path.basename(contractBaseName, SOLIDITY_FILE_EXTENSION);
const currentArtifactPath = `${this.artifactsDir}/${contractName}.json`;
const sourceHash = `0x${ethUtil.sha3(source).toString('hex')}`;
let currentArtifactString: string;
let currentArtifact: ContractArtifact;
let oldNetworks: ContractNetworks;
let shouldCompile: boolean;
try {
const opts = {
encoding: 'utf8',
};
currentArtifactString = await fsWrapper.readFileAsync(currentArtifactPath, opts);
currentArtifact = JSON.parse(currentArtifactString);
oldNetworks = currentArtifact.networks;
const oldNetwork: ContractData = oldNetworks[this.networkId];
shouldCompile = _.isUndefined(oldNetwork) ||
oldNetwork.keccak256 !== sourceHash ||
oldNetwork.optimizer_enabled !== this.optimizerEnabled;
} catch (err) {
shouldCompile = true;
}
if (!shouldCompile) {
return;
}
const input = {
[contractBaseName]: source,
};
const solcVersion = parseSolidityVersion(source);
const fullSolcVersion = binPaths[solcVersion];
const solcBinPath = `./../solc/solc_bin/${fullSolcVersion}`;
const solcBin = require(solcBinPath);
const solcInstance = solc.setupMethods(solcBin);
utils.consoleLog(`Compiling ${contractBaseName}...`);
const sourcesToCompile = {
sources: input,
};
const compiled = solcInstance.compile(sourcesToCompile,
this.optimizerEnabled,
this.findImportsIfSourcesExist.bind(this));
if (!_.isUndefined(compiled.errors)) {
_.each(compiled.errors, errMsg => {
const normalizedErrMsg = getNormalizedErrMsg(errMsg);
this.solcErrors.add(normalizedErrMsg);
});
}
const contractIdentifier = `${contractBaseName}:${contractName}`;
const abi: Web3.ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface);
const unlinked_binary = `0x${compiled.contracts[contractIdentifier].bytecode}`;
const updated_at = Date.now();
const contractData: ContractData = {
solc_version: solcVersion,
keccak256: sourceHash,
optimizer_enabled: this.optimizerEnabled,
abi,
unlinked_binary,
updated_at,
};
let newArtifact: ContractArtifact;
if (!_.isUndefined(currentArtifactString)) {
newArtifact = {
...currentArtifact,
networks: {
...oldNetworks,
[this.networkId]: contractData,
},
};
} else {
newArtifact = {
contract_name: contractName,
networks: {
[this.networkId]: contractData,
},
};
}
const artifactString = utils.stringifyWithFormatting(newArtifact);
await fsWrapper.writeFileAsync(currentArtifactPath, artifactString);
utils.consoleLog(`${contractBaseName} artifact saved!`);
}
/**
* Callback to resolve dependencies with `solc.compile`.
* Throws error if contractSources not yet initialized.
* @param importPath Path to an imported dependency.
* @return Import contents object containing source code of dependency.
*/
private findImportsIfSourcesExist(importPath: string): ImportContents {
if (_.isUndefined(this.contractSourcesIfExists)) {
throw new Error('Contract sources not yet initialized');
}
const contractBaseName = path.basename(importPath);
const source = this.contractSourcesIfExists[contractBaseName];
const importContents: ImportContents = {
contents: source,
};
return importContents;
}
/**
* Creates the artifacts directory if it does not already exist.
*/
private async createArtifactsDirIfDoesNotExistAsync(): Promise<void> {
if (!fsWrapper.doesPathExistSync(this.artifactsDir)) {
utils.consoleLog('Creating artifacts directory...');
await fsWrapper.mkdirAsync(this.artifactsDir);
}
}
}

View File

@@ -0,0 +1,181 @@
import promisify = require('es6-promisify');
import * as _ from 'lodash';
import * as Web3 from 'web3';
import {Contract} from './utils/contract';
import {encoder} from './utils/encoder';
import {fsWrapper} from './utils/fs_wrapper';
import {
ContractArtifact,
ContractData,
DeployerOptions,
} from './utils/types';
import {utils} from './utils/utils';
import {Web3Wrapper} from './utils/web3_wrapper';
// Gas added to gas estimate to make sure there is sufficient gas for deployment.
const EXTRA_GAS = 200000;
export class Deployer {
public web3Wrapper: Web3Wrapper;
private artifactsDir: string;
private jsonrpcPort: number;
private networkId: number;
private defaults: Partial<Web3.TxData>;
constructor(opts: DeployerOptions) {
this.artifactsDir = opts.artifactsDir;
this.jsonrpcPort = opts.jsonrpcPort;
this.networkId = opts.networkId;
const jsonrpcUrl = `http://localhost:${this.jsonrpcPort}`;
const web3Provider = new Web3.providers.HttpProvider(jsonrpcUrl);
this.defaults = opts.defaults;
this.web3Wrapper = new Web3Wrapper(web3Provider, this.defaults);
}
/**
* Loads contract artifact and deploys contract with given arguments.
* @param contractName Name of the contract to deploy. Must match name of an artifact in artifacts directory.
* @param args Array of contract constructor arguments.
* @return Deployed contract instance.
*/
public async deployAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> {
const contractArtifact: ContractArtifact = this.loadContractArtifactIfExists(contractName);
const contractData: ContractData = this.getContractDataFromArtifactIfExists(contractArtifact);
const data = contractData.unlinked_binary;
const from = await this.getFromAddressAsync();
const gas = await this.getAllowableGasEstimateAsync(data);
const txData = {
gasPrice: this.defaults.gasPrice,
from,
data,
gas,
};
const abi = contractData.abi;
const web3ContractInstance = await this.deployFromAbiAsync(abi, args, txData);
utils.consoleLog(`${contractName}.sol successfully deployed at ${web3ContractInstance.address}`);
const contractInstance = new Contract(web3ContractInstance, this.defaults);
return contractInstance;
}
/**
* Loads contract artifact, deploys with given arguments, and saves updated data to artifact.
* @param contractName Name of the contract to deploy. Must match name of an artifact in artifacts directory.
* @param args Array of contract constructor arguments.
* @return Deployed contract instance.
*/
public async deployAndSaveAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> {
const contractInstance = await this.deployAsync(contractName, args);
await this.saveContractDataToArtifactAsync(contractName, contractInstance.address, args);
return contractInstance;
}
/**
* Deploys a contract given its ABI, arguments, and transaction data.
* @param abi ABI of contract to deploy.
* @param args Constructor arguments to use in deployment.
* @param txData Tx options used for deployment.
* @return Promise that resolves to a web3 contract instance.
*/
private async deployFromAbiAsync(abi: Web3.ContractAbi, args: any[], txData: Web3.TxData): Promise<any> {
const contract: Web3.Contract<Web3.ContractInstance> = this.web3Wrapper.getContractFromAbi(abi);
const deployPromise = new Promise((resolve, reject) => {
/**
* Contract is inferred as 'any' because TypeScript
* is not able to read 'new' from the Contract interface
*/
(contract as any).new(...args, txData, (err: Error, res: any): any => {
if (err) {
reject(err);
} else if (_.isUndefined(res.address) && !_.isUndefined(res.transactionHash)) {
utils.consoleLog(`transactionHash: ${res.transactionHash}`);
} else {
resolve(res);
}
});
});
return deployPromise;
}
/**
* Updates a contract artifact's address and encoded constructor arguments.
* @param contractName Name of contract. Must match an existing artifact.
* @param contractAddress Contract address to save to artifact.
* @param args Contract constructor arguments that will be encoded and saved to artifact.
*/
private async saveContractDataToArtifactAsync(contractName: string,
contractAddress: string, args: any[]): Promise<void> {
const contractArtifact: ContractArtifact = this.loadContractArtifactIfExists(contractName);
const contractData: ContractData = this.getContractDataFromArtifactIfExists(contractArtifact);
const abi = contractData.abi;
const encodedConstructorArgs = encoder.encodeConstructorArgsFromAbi(args, abi);
const newContractData = {
...contractData,
address: contractAddress,
constructor_args: encodedConstructorArgs,
};
const newArtifact = {
...contractArtifact,
networks: {
...contractArtifact.networks,
[this.networkId]: newContractData,
},
};
const artifactString = utils.stringifyWithFormatting(newArtifact);
const artifactPath = `${this.artifactsDir}/${contractName}.json`;
await fsWrapper.writeFileAsync(artifactPath, artifactString);
}
/**
* Loads a contract artifact, if it exists.
* @param contractName Name of the contract, without the extension.
* @return The contract artifact.
*/
private loadContractArtifactIfExists(contractName: string): ContractArtifact {
const artifactPath = `${this.artifactsDir}/${contractName}.json`;
try {
const contractArtifact: ContractArtifact = require(artifactPath);
return contractArtifact;
} catch (err) {
throw new Error(`Artifact not found for contract: ${contractName}`);
}
}
/**
* Gets data for current networkId stored in artifact.
* @param contractArtifact The contract artifact.
* @return Network specific contract data.
*/
private getContractDataFromArtifactIfExists(contractArtifact: ContractArtifact): ContractData {
const contractData = contractArtifact.networks[this.networkId];
if (_.isUndefined(contractData)) {
throw new Error(`Data not found in artifact for contract: ${contractArtifact.contract_name}`);
}
return contractData;
}
/**
* Gets the address to use for sending a transaction.
* @return The default from address. If not specified, returns the first address accessible by web3.
*/
private async getFromAddressAsync(): Promise<string> {
let from: string;
if (_.isUndefined(this.defaults.from)) {
const accounts = await this.web3Wrapper.getAvailableAddressesAsync();
from = accounts[0];
} else {
from = this.defaults.from;
}
return from;
}
/**
* Estimates the gas required for a transaction.
* If gas would be over the block gas limit, the max allowable gas is returned instead.
* @param data Bytecode to estimate gas for.
* @return Gas estimate for transaction data.
*/
private async getAllowableGasEstimateAsync(data: string): Promise<number> {
const block = await this.web3Wrapper.getBlockAsync('latest');
let gas: number;
try {
const gasEstimate: number = await this.web3Wrapper.estimateGasAsync({data});
gas = Math.min(gasEstimate + EXTRA_GAS, block.gasLimit);
} catch (err) {
gas = block.gasLimit;
}
return gas;
}
}

View File

@@ -0,0 +1,3 @@
export const constants = {
NULL_BYTES: '0x',
};

View File

@@ -0,0 +1,81 @@
import {schemas, SchemaValidator} from '@0xproject/json-schemas';
import promisify = require('es6-promisify');
import * as _ from 'lodash';
import * as Web3 from 'web3';
import {AbiType} from './types';
export class Contract implements Web3.ContractInstance {
public address: string;
public abi: Web3.ContractAbi;
private contract: Web3.ContractInstance;
private defaults: Partial<Web3.TxData>;
private validator: SchemaValidator;
// This class instance is going to be populated with functions and events depending on the ABI
// and we don't know their types in advance
[name: string]: any;
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<Web3.TxData>) {
this.contract = web3ContractInstance;
this.address = web3ContractInstance.address;
this.abi = web3ContractInstance.abi;
this.defaults = defaults;
this.populateEvents();
this.populateFunctions();
this.validator = new SchemaValidator();
}
private populateFunctions(): void {
const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Function);
_.forEach(functionsAbi, (functionAbi: Web3.MethodAbi) => {
if (functionAbi.constant) {
const cbStyleCallFunction = this.contract[functionAbi.name].call;
this[functionAbi.name] = {
callAsync: promisify(cbStyleCallFunction, this.contract),
};
} else {
const cbStyleFunction = this.contract[functionAbi.name];
const cbStyleEstimateGasFunction = this.contract[functionAbi.name].estimateGas;
this[functionAbi.name] = {
estimateGasAsync: promisify(cbStyleEstimateGasFunction, this.contract),
sendTransactionAsync: this.promisifyWithDefaultParams(cbStyleFunction),
};
}
});
}
private populateEvents(): void {
const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Event);
_.forEach(eventsAbi, (eventAbi: Web3.EventAbi) => {
this[eventAbi.name] = this.contract[eventAbi.name];
});
}
private promisifyWithDefaultParams(fn: (...args: any[]) => void): (...args: any[]) => Promise<any> {
const promisifiedWithDefaultParams = async (...args: any[]) => {
const promise = new Promise((resolve, reject) => {
const lastArg = args[args.length - 1];
let txData: Partial<Web3.TxData> = {};
if (this.isTxData(lastArg)) {
txData = args.pop();
}
txData = {
...this.defaults,
...txData,
};
const callback = (err: Error, data: any) => {
if (_.isNull(err)) {
resolve(data);
} else {
reject(err);
}
};
args.push(txData);
args.push(callback);
fn.apply(this.contract, args);
});
return promise;
};
return promisifiedWithDefaultParams;
}
private isTxData(lastArg: any): boolean {
const isValid = this.validator.isValid(lastArg, schemas.txDataSchema);
return isValid;
}
}

View File

@@ -0,0 +1,20 @@
import * as _ from 'lodash';
import * as Web3 from 'web3';
import * as web3Abi from 'web3-eth-abi';
import {AbiType} from './types';
export const encoder = {
encodeConstructorArgsFromAbi(args: any[], abi: Web3.ContractAbi): string {
const constructorTypes: string[] = [];
_.each(abi, (element: Web3.AbiDefinition) => {
if (element.type === AbiType.Constructor) {
_.each(element.inputs, (input: Web3.FunctionParameter) => {
constructorTypes.push(input.type);
});
}
});
const encodedParameters = web3Abi.encodeParameters(constructorTypes, args);
return encodedParameters;
},
};

View File

@@ -0,0 +1,11 @@
import promisify = require('es6-promisify');
import * as fs from 'fs';
export const fsWrapper = {
readdirAsync: promisify(fs.readdir),
readFileAsync: promisify(fs.readFile),
writeFileAsync: promisify(fs.writeFile),
mkdirAsync: promisify(fs.mkdir),
doesPathExistSync: fs.existsSync,
removeFileAsync: promisify(fs.unlink),
};

View File

@@ -0,0 +1,15 @@
import promisify = require('es6-promisify');
import * as Web3 from 'web3';
import {Web3Wrapper} from './web3_wrapper';
export const network = {
async getNetworkIdIfExistsAsync(port: number): Promise<number> {
const url = `http://localhost:${port}`;
const web3Provider = new Web3.providers.HttpProvider(url);
const defaults = {};
const web3Wrapper = new Web3Wrapper(web3Provider, defaults);
const networkIdIfExists = await web3Wrapper.getNetworkIdIfExistsAsync();
return networkIdIfExists;
},
};

View File

@@ -0,0 +1,95 @@
import * as Web3 from 'web3';
export enum AbiType {
Function = 'function',
Constructor = 'constructor',
Event = 'event',
Fallback = 'fallback',
}
export interface ContractArtifact {
contract_name: string;
networks: ContractNetworks;
}
export interface ContractNetworks {
[key: number]: ContractData;
}
export interface ContractData {
solc_version: string;
optimizer_enabled: number;
keccak256: string;
abi: Web3.ContractAbi;
unlinked_binary: string;
address?: string;
constructor_args?: string;
updated_at: number;
}
export interface SolcErrors {
[key: string]: boolean;
}
export interface CliOptions {
artifactsDir: string;
contractsDir: string;
jsonrpcPort: number;
networkId: number;
shouldOptimize: boolean;
gasPrice: string;
account?: string;
contract?: string;
args?: string;
}
export interface CompilerOptions {
contractsDir: string;
networkId: number;
optimizerEnabled: number;
artifactsDir: string;
}
export interface DeployerOptions {
artifactsDir: string;
jsonrpcPort: number;
networkId: number;
defaults: Partial<Web3.TxData>;
}
export interface ContractSources {
[key: string]: string;
}
export interface ImportContents {
contents: string;
}
// TODO: Consolidate with 0x.js definitions once types are moved into a separate package.
export enum ZeroExError {
ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST',
ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST',
UnhandledError = 'UNHANDLED_ERROR',
UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES',
InvalidSignature = 'INVALID_SIGNATURE',
ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER',
InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT',
InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL',
InvalidJump = 'INVALID_JUMP',
OutOfGas = 'OUT_OF_GAS',
NoNetworkId = 'NO_NETWORK_ID',
SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND',
}
export interface Token {
address?: string;
name: string;
symbol: string;
decimals: number;
ipfsHash: string;
swarmHash: string;
}
export type DoneCallback = (err?: Error) => void;

View File

@@ -0,0 +1,13 @@
export const utils = {
consoleLog(message: string): void {
/* tslint:disable */
console.log(message);
/* tslint:enable */
},
stringifyWithFormatting(obj: any): string {
const jsonReplacer: null = null;
const numberOfJsonSpaces = 4;
const stringifiedObj = JSON.stringify(obj, jsonReplacer, numberOfJsonSpaces);
return stringifiedObj;
},
};

View File

@@ -0,0 +1,132 @@
import BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
import * as _ from 'lodash';
import * as Web3 from 'web3';
import {Contract} from './contract';
import {ZeroExError} from './types';
export class Web3Wrapper {
private web3: Web3;
private defaults: Partial<Web3.TxData>;
private networkIdIfExists?: number;
private jsonRpcRequestId: number;
constructor(provider: Web3.Provider, defaults: Partial<Web3.TxData>) {
this.web3 = new Web3();
this.web3.setProvider(provider);
this.defaults = defaults;
this.jsonRpcRequestId = 0;
}
public setProvider(provider: Web3.Provider) {
delete this.networkIdIfExists;
this.web3.setProvider(provider);
}
public isAddress(address: string): boolean {
return this.web3.isAddress(address);
}
public getContractFromAbi(abi: Web3.ContractAbi): Web3.Contract<Web3.ContractInstance> {
const contract = this.web3.eth.contract(abi);
return contract;
}
public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> {
const addresses = await this.getAvailableAddressesAsync();
return _.includes(addresses, senderAddress);
}
public async getNodeVersionAsync(): Promise<string> {
const nodeVersion = await promisify(this.web3.version.getNode)();
return nodeVersion;
}
public async getTransactionReceiptAsync(txHash: string): Promise<Web3.TransactionReceipt> {
const transactionReceipt = await promisify(this.web3.eth.getTransactionReceipt)(txHash);
return transactionReceipt;
}
public getCurrentProvider(): Web3.Provider {
return this.web3.currentProvider;
}
public async getNetworkIdIfExistsAsync(): Promise<number|undefined> {
if (!_.isUndefined(this.networkIdIfExists)) {
return this.networkIdIfExists;
}
try {
const networkId = await this.getNetworkAsync();
this.networkIdIfExists = Number(networkId);
return this.networkIdIfExists;
} catch (err) {
return undefined;
}
}
public toWei(ethAmount: BigNumber): BigNumber {
const balanceWei = this.web3.toWei(ethAmount, 'ether');
return balanceWei;
}
public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> {
let balanceInWei = await promisify(this.web3.eth.getBalance)(owner);
balanceInWei = new BigNumber(balanceInWei);
return balanceInWei;
}
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
const code = await promisify(this.web3.eth.getCode)(address);
// Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
const codeIsEmpty = /^0x0{0,40}$/i.test(code);
return !codeIsEmpty;
}
public async signTransactionAsync(address: string, message: string): Promise<string> {
const signData = await promisify(this.web3.eth.sign)(address, message);
return signData;
}
public async getBlockAsync(blockParam: string|Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> {
const block = await promisify(this.web3.eth.getBlock)(blockParam);
return block;
}
public async getBlockTimestampAsync(blockParam: string|Web3.BlockParam): Promise<number> {
const {timestamp} = await this.getBlockAsync(blockParam);
return timestamp;
}
public async getAvailableAddressesAsync(): Promise<string[]> {
const addresses: string[] = await promisify(this.web3.eth.getAccounts)();
return addresses;
}
public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> {
let fromBlock = filter.fromBlock;
if (_.isNumber(fromBlock)) {
fromBlock = this.web3.toHex(fromBlock);
}
let toBlock = filter.toBlock;
if (_.isNumber(toBlock)) {
toBlock = this.web3.toHex(toBlock);
}
const serializedFilter = {
...filter,
fromBlock,
toBlock,
};
const payload = {
jsonrpc: '2.0',
id: this.jsonRpcRequestId++,
method: 'eth_getLogs',
params: [serializedFilter],
};
const logs = await this.sendRawPayloadAsync(payload);
return logs;
}
public async estimateGasAsync(callData: Web3.CallData): Promise<number> {
const gasEstimate = await promisify(this.web3.eth.estimateGas)(callData);
return gasEstimate;
}
private getContractInstance<A extends Web3.ContractInstance>(abi: Web3.ContractAbi, address: string): A {
const web3ContractInstance = this.web3.eth.contract(abi).at(address);
const contractInstance = new Contract(web3ContractInstance, this.defaults) as any as A;
return contractInstance;
}
private async getNetworkAsync(): Promise<number> {
const networkId = await promisify(this.web3.version.getNetwork)();
return networkId;
}
private async sendRawPayloadAsync(payload: Web3.JSONRPCRequestPayload): Promise<any> {
const sendAsync = this.web3.currentProvider.sendAsync.bind(this.web3.currentProvider);
const response = await promisify(sendAsync)(payload);
const result = response.result;
return result;
}
}

View File

@@ -0,0 +1,91 @@
import * as chai from 'chai';
import 'mocha';
import {Compiler} from './../src/compiler';
import {Deployer} from './../src/deployer';
import {fsWrapper} from './../src/utils/fs_wrapper';
import {CompilerOptions, ContractArtifact, ContractData, DeployerOptions, DoneCallback} from './../src/utils/types';
import {constructor_args, exchange_binary} from './fixtures/exchange_bin';
import {constants} from './util/constants';
const expect = chai.expect;
const artifactsDir = `${__dirname}/fixtures/artifacts`;
const contractsDir = `${__dirname}/fixtures/contracts`;
const exchangeArtifactPath = `${artifactsDir}/Exchange.json`;
const compilerOpts: CompilerOptions = {
artifactsDir,
contractsDir,
networkId: constants.networkId,
optimizerEnabled: constants.optimizerEnabled,
};
const compiler = new Compiler(compilerOpts);
const deployerOpts: DeployerOptions = {
artifactsDir,
networkId: constants.networkId,
jsonrpcPort: constants.jsonrpcPort,
defaults: {
gasPrice: constants.gasPrice,
},
};
const deployer = new Deployer(deployerOpts);
/* tslint:disable */
beforeEach(function(done: DoneCallback) {
this.timeout(constants.timeoutMs);
(async () => {
if (fsWrapper.doesPathExistSync(exchangeArtifactPath)) {
await fsWrapper.removeFileAsync(exchangeArtifactPath);
}
await compiler.compileAllAsync();
done();
})().catch(done);
});
/* tslint:enable */
describe('#Compiler', () => {
it('should create an Exchange artifact with the correct unlinked binary', async () => {
const opts = {
encoding: 'utf8',
};
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
const exchangeContractData: ContractData = exchangeArtifact.networks[constants.networkId];
// The last 43 bytes of the binaries are metadata which may not be equivalent
const unlinkedBinaryWithoutMetadata = exchangeContractData.unlinked_binary.slice(0, -86);
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86);
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
});
});
describe('#Deployer', () => {
describe('#deployAsync', () => {
it('should deploy the Exchange contract without updating the Exchange artifact', async () => {
const exchangeConstructorArgs = [constants.zrxTokenAddress, constants.tokenTransferProxyAddress];
const exchangeContractInstance = await deployer.deployAsync('Exchange', exchangeConstructorArgs);
const opts = {
encoding: 'utf8',
};
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
const exchangeContractData: ContractData = exchangeArtifact.networks[constants.networkId];
const exchangeAddress = exchangeContractInstance.address;
expect(exchangeAddress).to.not.equal(undefined);
expect(exchangeContractData.address).to.equal(undefined);
expect(exchangeContractData.constructor_args).to.equal(undefined);
});
});
describe('#deployAndSaveAsync', () => {
it('should save the correct contract address and constructor arguments to the Exchange artifact', async () => {
const exchangeConstructorArgs = [constants.zrxTokenAddress, constants.tokenTransferProxyAddress];
const exchangeContractInstance = await deployer.deployAndSaveAsync('Exchange', exchangeConstructorArgs);
const opts = {
encoding: 'utf8',
};
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
const exchangeContractData: ContractData = exchangeArtifact.networks[constants.networkId];
const exchangeAddress = exchangeContractInstance.address;
expect(exchangeAddress).to.be.equal(exchangeContractData.address);
expect(constructor_args).to.be.equal(exchangeContractData.constructor_args);
});
});
});

View File

@@ -0,0 +1,602 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.14;
import "./TokenTransferProxy.sol";
import "./base/Token.sol";
import "./base/SafeMath.sol";
/// @title Exchange - Facilitates exchange of ERC20 tokens.
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
contract Exchange is SafeMath {
// Error Codes
enum Errors {
ORDER_EXPIRED, // Order has already expired
ORDER_FULLY_FILLED_OR_CANCELLED, // Order has already been fully filled or cancelled
ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
INSUFFICIENT_BALANCE_OR_ALLOWANCE // Insufficient balance or allowance for token transfer
}
string constant public VERSION = "1.0.0";
uint16 constant public EXTERNAL_QUERY_GAS_LIMIT = 4999; // Changes to state require at least 5000 gas
address public ZRX_TOKEN_CONTRACT;
address public TOKEN_TRANSFER_PROXY_CONTRACT;
// Mappings of orderHash => amounts of takerTokenAmount filled or cancelled.
mapping (bytes32 => uint) public filled;
mapping (bytes32 => uint) public cancelled;
event LogFill(
address indexed maker,
address taker,
address indexed feeRecipient,
address makerToken,
address takerToken,
uint filledMakerTokenAmount,
uint filledTakerTokenAmount,
uint paidMakerFee,
uint paidTakerFee,
bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
bytes32 orderHash
);
event LogCancel(
address indexed maker,
address indexed feeRecipient,
address makerToken,
address takerToken,
uint cancelledMakerTokenAmount,
uint cancelledTakerTokenAmount,
bytes32 indexed tokens,
bytes32 orderHash
);
event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
struct Order {
address maker;
address taker;
address makerToken;
address takerToken;
address feeRecipient;
uint makerTokenAmount;
uint takerTokenAmount;
uint makerFee;
uint takerFee;
uint expirationTimestampInSec;
bytes32 orderHash;
}
function Exchange(address _zrxToken, address _tokenTransferProxy) {
ZRX_TOKEN_CONTRACT = _zrxToken;
TOKEN_TRANSFER_PROXY_CONTRACT = _tokenTransferProxy;
}
/*
* Core exchange functions
*/
/// @dev Fills the input order.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param fillTakerTokenAmount Desired amount of takerToken to fill.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfer will fail before attempting.
/// @param v ECDSA signature parameter v.
/// @param r ECDSA signature parameters r.
/// @param s ECDSA signature parameters s.
/// @return Total amount of takerToken filled in trade.
function fillOrder(
address[5] orderAddresses,
uint[6] orderValues,
uint fillTakerTokenAmount,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8 v,
bytes32 r,
bytes32 s)
public
returns (uint filledTakerTokenAmount)
{
Order memory order = Order({
maker: orderAddresses[0],
taker: orderAddresses[1],
makerToken: orderAddresses[2],
takerToken: orderAddresses[3],
feeRecipient: orderAddresses[4],
makerTokenAmount: orderValues[0],
takerTokenAmount: orderValues[1],
makerFee: orderValues[2],
takerFee: orderValues[3],
expirationTimestampInSec: orderValues[4],
orderHash: getOrderHash(orderAddresses, orderValues)
});
require(order.taker == address(0) || order.taker == msg.sender);
require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && fillTakerTokenAmount > 0);
require(isValidSignature(
order.maker,
order.orderHash,
v,
r,
s
));
if (block.timestamp >= order.expirationTimestampInSec) {
LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
return 0;
}
uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
filledTakerTokenAmount = min256(fillTakerTokenAmount, remainingTakerTokenAmount);
if (filledTakerTokenAmount == 0) {
LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
return 0;
}
if (isRoundingError(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount)) {
LogError(uint8(Errors.ROUNDING_ERROR_TOO_LARGE), order.orderHash);
return 0;
}
if (!shouldThrowOnInsufficientBalanceOrAllowance && !isTransferable(order, filledTakerTokenAmount)) {
LogError(uint8(Errors.INSUFFICIENT_BALANCE_OR_ALLOWANCE), order.orderHash);
return 0;
}
uint filledMakerTokenAmount = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
uint paidMakerFee;
uint paidTakerFee;
filled[order.orderHash] = safeAdd(filled[order.orderHash], filledTakerTokenAmount);
require(transferViaTokenTransferProxy(
order.makerToken,
order.maker,
msg.sender,
filledMakerTokenAmount
));
require(transferViaTokenTransferProxy(
order.takerToken,
msg.sender,
order.maker,
filledTakerTokenAmount
));
if (order.feeRecipient != address(0)) {
if (order.makerFee > 0) {
paidMakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerFee);
require(transferViaTokenTransferProxy(
ZRX_TOKEN_CONTRACT,
order.maker,
order.feeRecipient,
paidMakerFee
));
}
if (order.takerFee > 0) {
paidTakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.takerFee);
require(transferViaTokenTransferProxy(
ZRX_TOKEN_CONTRACT,
msg.sender,
order.feeRecipient,
paidTakerFee
));
}
}
LogFill(
order.maker,
msg.sender,
order.feeRecipient,
order.makerToken,
order.takerToken,
filledMakerTokenAmount,
filledTakerTokenAmount,
paidMakerFee,
paidTakerFee,
keccak256(order.makerToken, order.takerToken),
order.orderHash
);
return filledTakerTokenAmount;
}
/// @dev Cancels the input order.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param cancelTakerTokenAmount Desired amount of takerToken to cancel in order.
/// @return Amount of takerToken cancelled.
function cancelOrder(
address[5] orderAddresses,
uint[6] orderValues,
uint cancelTakerTokenAmount)
public
returns (uint)
{
Order memory order = Order({
maker: orderAddresses[0],
taker: orderAddresses[1],
makerToken: orderAddresses[2],
takerToken: orderAddresses[3],
feeRecipient: orderAddresses[4],
makerTokenAmount: orderValues[0],
takerTokenAmount: orderValues[1],
makerFee: orderValues[2],
takerFee: orderValues[3],
expirationTimestampInSec: orderValues[4],
orderHash: getOrderHash(orderAddresses, orderValues)
});
require(order.maker == msg.sender);
require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && cancelTakerTokenAmount > 0);
if (block.timestamp >= order.expirationTimestampInSec) {
LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
return 0;
}
uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
uint cancelledTakerTokenAmount = min256(cancelTakerTokenAmount, remainingTakerTokenAmount);
if (cancelledTakerTokenAmount == 0) {
LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
return 0;
}
cancelled[order.orderHash] = safeAdd(cancelled[order.orderHash], cancelledTakerTokenAmount);
LogCancel(
order.maker,
order.feeRecipient,
order.makerToken,
order.takerToken,
getPartialAmount(cancelledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount),
cancelledTakerTokenAmount,
keccak256(order.makerToken, order.takerToken),
order.orderHash
);
return cancelledTakerTokenAmount;
}
/*
* Wrapper functions
*/
/// @dev Fills an order with specified parameters and ECDSA signature, throws if specified amount not filled entirely.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param fillTakerTokenAmount Desired amount of takerToken to fill.
/// @param v ECDSA signature parameter v.
/// @param r ECDSA signature parameters r.
/// @param s ECDSA signature parameters s.
function fillOrKillOrder(
address[5] orderAddresses,
uint[6] orderValues,
uint fillTakerTokenAmount,
uint8 v,
bytes32 r,
bytes32 s)
public
{
require(fillOrder(
orderAddresses,
orderValues,
fillTakerTokenAmount,
false,
v,
r,
s
) == fillTakerTokenAmount);
}
/// @dev Synchronously executes multiple fill orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
function batchFillOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] fillTakerTokenAmounts,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
fillOrder(
orderAddresses[i],
orderValues[i],
fillTakerTokenAmounts[i],
shouldThrowOnInsufficientBalanceOrAllowance,
v[i],
r[i],
s[i]
);
}
}
/// @dev Synchronously executes multiple fillOrKill orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
function batchFillOrKillOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] fillTakerTokenAmounts,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
fillOrKillOrder(
orderAddresses[i],
orderValues[i],
fillTakerTokenAmounts[i],
v[i],
r[i],
s[i]
);
}
}
/// @dev Synchronously executes multiple fill orders in a single transaction until total fillTakerTokenAmount filled.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmount Desired total amount of takerToken to fill in orders.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
/// @return Total amount of fillTakerTokenAmount filled in orders.
function fillOrdersUpTo(
address[5][] orderAddresses,
uint[6][] orderValues,
uint fillTakerTokenAmount,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
returns (uint)
{
uint filledTakerTokenAmount = 0;
for (uint i = 0; i < orderAddresses.length; i++) {
require(orderAddresses[i][3] == orderAddresses[0][3]); // takerToken must be the same for each order
filledTakerTokenAmount = safeAdd(filledTakerTokenAmount, fillOrder(
orderAddresses[i],
orderValues[i],
safeSub(fillTakerTokenAmount, filledTakerTokenAmount),
shouldThrowOnInsufficientBalanceOrAllowance,
v[i],
r[i],
s[i]
));
if (filledTakerTokenAmount == fillTakerTokenAmount) break;
}
return filledTakerTokenAmount;
}
/// @dev Synchronously cancels multiple orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param cancelTakerTokenAmounts Array of desired amounts of takerToken to cancel in orders.
function batchCancelOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] cancelTakerTokenAmounts)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
cancelOrder(
orderAddresses[i],
orderValues[i],
cancelTakerTokenAmounts[i]
);
}
}
/*
* Constant public functions
*/
/// @dev Calculates Keccak-256 hash of order with specified parameters.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @return Keccak-256 hash of order.
function getOrderHash(address[5] orderAddresses, uint[6] orderValues)
public
constant
returns (bytes32)
{
return keccak256(
address(this),
orderAddresses[0], // maker
orderAddresses[1], // taker
orderAddresses[2], // makerToken
orderAddresses[3], // takerToken
orderAddresses[4], // feeRecipient
orderValues[0], // makerTokenAmount
orderValues[1], // takerTokenAmount
orderValues[2], // makerFee
orderValues[3], // takerFee
orderValues[4], // expirationTimestampInSec
orderValues[5] // salt
);
}
/// @dev Verifies that an order signature is valid.
/// @param signer address of signer.
/// @param hash Signed Keccak-256 hash.
/// @param v ECDSA signature parameter v.
/// @param r ECDSA signature parameters r.
/// @param s ECDSA signature parameters s.
/// @return Validity of order signature.
function isValidSignature(
address signer,
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s)
public
constant
returns (bool)
{
return signer == ecrecover(
keccak256("\x19Ethereum Signed Message:\n32", hash),
v,
r,
s
);
}
/// @dev Checks if rounding error > 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to multiply with numerator/denominator.
/// @return Rounding error is present.
function isRoundingError(uint numerator, uint denominator, uint target)
public
constant
returns (bool)
{
uint remainder = mulmod(target, numerator, denominator);
if (remainder == 0) return false; // No rounding error.
uint errPercentageTimes1000000 = safeDiv(
safeMul(remainder, 1000000),
safeMul(numerator, target)
);
return errPercentageTimes1000000 > 1000;
}
/// @dev Calculates partial value given a numerator and denominator.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function getPartialAmount(uint numerator, uint denominator, uint target)
public
constant
returns (uint)
{
return safeDiv(safeMul(numerator, target), denominator);
}
/// @dev Calculates the sum of values already filled and cancelled for a given order.
/// @param orderHash The Keccak-256 hash of the given order.
/// @return Sum of values already filled and cancelled.
function getUnavailableTakerTokenAmount(bytes32 orderHash)
public
constant
returns (uint)
{
return safeAdd(filled[orderHash], cancelled[orderHash]);
}
/*
* Internal functions
*/
/// @dev Transfers a token using TokenTransferProxy transferFrom function.
/// @param token Address of token to transferFrom.
/// @param from Address transfering token.
/// @param to Address receiving token.
/// @param value Amount of token to transfer.
/// @return Success of token transfer.
function transferViaTokenTransferProxy(
address token,
address from,
address to,
uint value)
internal
returns (bool)
{
return TokenTransferProxy(TOKEN_TRANSFER_PROXY_CONTRACT).transferFrom(token, from, to, value);
}
/// @dev Checks if any order transfers will fail.
/// @param order Order struct of params that will be checked.
/// @param fillTakerTokenAmount Desired amount of takerToken to fill.
/// @return Predicted result of transfers.
function isTransferable(Order order, uint fillTakerTokenAmount)
internal
constant // The called token contracts may attempt to change state, but will not be able to due to gas limits on getBalance and getAllowance.
returns (bool)
{
address taker = msg.sender;
uint fillMakerTokenAmount = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
if (order.feeRecipient != address(0)) {
bool isMakerTokenZRX = order.makerToken == ZRX_TOKEN_CONTRACT;
bool isTakerTokenZRX = order.takerToken == ZRX_TOKEN_CONTRACT;
uint paidMakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerFee);
uint paidTakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.takerFee);
uint requiredMakerZRX = isMakerTokenZRX ? safeAdd(fillMakerTokenAmount, paidMakerFee) : paidMakerFee;
uint requiredTakerZRX = isTakerTokenZRX ? safeAdd(fillTakerTokenAmount, paidTakerFee) : paidTakerFee;
if ( getBalance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
|| getAllowance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
|| getBalance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
|| getAllowance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
) return false;
if (!isMakerTokenZRX && ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount // Don't double check makerToken if ZRX
|| getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount)
) return false;
if (!isTakerTokenZRX && ( getBalance(order.takerToken, taker) < fillTakerTokenAmount // Don't double check takerToken if ZRX
|| getAllowance(order.takerToken, taker) < fillTakerTokenAmount)
) return false;
} else if ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount
|| getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount
|| getBalance(order.takerToken, taker) < fillTakerTokenAmount
|| getAllowance(order.takerToken, taker) < fillTakerTokenAmount
) return false;
return true;
}
/// @dev Get token balance of an address.
/// @param token Address of token.
/// @param owner Address of owner.
/// @return Token balance of owner.
function getBalance(address token, address owner)
internal
constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
returns (uint)
{
return Token(token).balanceOf.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner); // Limit gas to prevent reentrancy
}
/// @dev Get allowance of token given to TokenTransferProxy by an address.
/// @param token Address of token.
/// @param owner Address of owner.
/// @return Allowance of token given to TokenTransferProxy by owner.
function getAllowance(address token, address owner)
internal
constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
returns (uint)
{
return Token(token).allowance.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner, TOKEN_TRANSFER_PROXY_CONTRACT); // Limit gas to prevent reentrancy
}
}

View File

@@ -0,0 +1,115 @@
/*
Copyright 2017 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.14;
import "./base/Token.sol";
import "./base/Ownable.sol";
/// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance.
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
contract TokenTransferProxy is Ownable {
/// @dev Only authorized addresses can invoke functions with this modifier.
modifier onlyAuthorized {
require(authorized[msg.sender]);
_;
}
modifier targetAuthorized(address target) {
require(authorized[target]);
_;
}
modifier targetNotAuthorized(address target) {
require(!authorized[target]);
_;
}
mapping (address => bool) public authorized;
address[] public authorities;
event LogAuthorizedAddressAdded(address indexed target, address indexed caller);
event LogAuthorizedAddressRemoved(address indexed target, address indexed caller);
/*
* Public functions
*/
/// @dev Authorizes an address.
/// @param target Address to authorize.
function addAuthorizedAddress(address target)
public
onlyOwner
targetNotAuthorized(target)
{
authorized[target] = true;
authorities.push(target);
LogAuthorizedAddressAdded(target, msg.sender);
}
/// @dev Removes authorizion of an address.
/// @param target Address to remove authorization from.
function removeAuthorizedAddress(address target)
public
onlyOwner
targetAuthorized(target)
{
delete authorized[target];
for (uint i = 0; i < authorities.length; i++) {
if (authorities[i] == target) {
authorities[i] = authorities[authorities.length - 1];
authorities.length -= 1;
break;
}
}
LogAuthorizedAddressRemoved(target, msg.sender);
}
/// @dev Calls into ERC20 Token contract, invoking transferFrom.
/// @param token Address of token to transfer.
/// @param from Address to transfer token from.
/// @param to Address to transfer token to.
/// @param value Amount of token to transfer.
/// @return Success of transfer.
function transferFrom(
address token,
address from,
address to,
uint value)
public
onlyAuthorized
returns (bool)
{
return Token(token).transferFrom(from, to, value);
}
/*
* Public constant functions
*/
/// @dev Gets all authorized addresses.
/// @return Array of authorized addresses.
function getAuthorizedAddresses()
public
constant
returns (address[])
{
return authorities;
}
}

View File

@@ -0,0 +1,27 @@
pragma solidity 0.4.14;
/*
* Ownable
*
* Base contract with an owner.
* Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
*/
contract Ownable {
address public owner;
function Ownable() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}

View File

@@ -0,0 +1,41 @@
pragma solidity 0.4.14;
contract SafeMath {
function safeMul(uint a, uint b) internal constant returns (uint256) {
uint c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function safeDiv(uint a, uint b) internal constant returns (uint256) {
uint c = a / b;
return c;
}
function safeSub(uint a, uint b) internal constant returns (uint256) {
assert(b <= a);
return a - b;
}
function safeAdd(uint a, uint b) internal constant returns (uint256) {
uint c = a + b;
assert(c >= a);
return c;
}
function max64(uint64 a, uint64 b) internal constant returns (uint64) {
return a >= b ? a : b;
}
function min64(uint64 a, uint64 b) internal constant returns (uint64) {
return a < b ? a : b;
}
function max256(uint256 a, uint256 b) internal constant returns (uint256) {
return a >= b ? a : b;
}
function min256(uint256 a, uint256 b) internal constant returns (uint256) {
return a < b ? a : b;
}
}

View File

@@ -0,0 +1,38 @@
pragma solidity 0.4.14;
contract Token {
/// @return total amount of tokens
function totalSupply() constant returns (uint supply) {}
/// @param _owner The address from which the balance will be retrieved
/// @return The balance
function balanceOf(address _owner) constant returns (uint balance) {}
/// @notice send `_value` token to `_to` from `msg.sender`
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transfer(address _to, uint _value) returns (bool success) {}
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transferFrom(address _from, address _to, uint _value) returns (bool success) {}
/// @notice `msg.sender` approves `_addr` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of wei to be approved for transfer
/// @return Whether the approval was successful or not
function approve(address _spender, uint _value) returns (bool success) {}
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return Amount of remaining tokens allowed to spent
function allowance(address _owner, address _spender) constant returns (uint remaining) {}
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
export const constants = {
networkId: 0,
jsonrpcPort: 8545,
optimizerEnabled: 0,
gasPrice: '20000000000',
timeoutMs: 12000,
zrxTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
tokenTransferProxyAddress: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
};

42
packages/contracts/globals.d.ts vendored Normal file
View File

@@ -0,0 +1,42 @@
declare module 'bn.js';
declare module 'ethereumjs-abi';
declare module 'chai-bignumber';
declare module 'dirty-chai';
declare module 'yargs';
// HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion
// interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise
// disallow `namespace`, we disable tslint for the following.
/* tslint:disable */
declare namespace Chai {
interface Assertion {
bignumber: Assertion;
}
}
/* tslint:enable */
declare module '*.json' {
const json: any;
/* tslint:disable */
export default json;
/* tslint:enable */
}
declare module 'solc' {
export function compile(sources: any, optimizerEnabled: number, findImports: (importPath: string) => any): any;
export function setupMethods(solcBin: any): any;
}
declare module 'es6-promisify' {
function promisify(original: any, settings?: any): ((...arg: any[]) => Promise<any>);
export = promisify;
}
declare module 'web3-eth-abi' {
export function encodeParameters(typesArray: string[], parameters: any[]): string;
}
// Truffle injects the following into the global scope
declare var artifacts: any;
declare var contract: any;

19
packages/contracts/globalsAugment.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
import {BigNumber} from 'bignumber.js';
// HACK: This module overrides the Chai namespace so that we can use BigNumber types inside.
// Source: https://github.com/Microsoft/TypeScript/issues/7352#issuecomment-191547232
declare global {
// HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion
// interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise
// disallow `namespace`, we disable tslint for the following.
/* tslint:disable */
namespace Chai {
interface NumberComparer {
(value: number | BigNumber, message?: string): Assertion;
}
interface NumericComparison {
greaterThan: NumberComparer;
}
}
/* tslint:enable */
}

View File

@@ -0,0 +1,6 @@
import {Artifacts} from '../util/artifacts';
const {Migrations} = new Artifacts(artifacts);
module.exports = (deployer: any) => {
deployer.deploy(Migrations);
};

View File

@@ -0,0 +1,43 @@
import {Artifacts} from '../util/artifacts';
import {ContractInstance, MultiSigConfigByNetwork} from '../util/types';
const {
MultiSigWalletWithTimeLock,
TokenTransferProxy,
EtherToken,
TokenRegistry,
} = new Artifacts(artifacts);
let multiSigConfigByNetwork: MultiSigConfigByNetwork;
try {
/* tslint:disable */
const multiSigConfig = require('./config/multisig');
multiSigConfigByNetwork = multiSigConfig.multiSig;
/* tslint:enable */
} catch (e) {
multiSigConfigByNetwork = {};
}
module.exports = (deployer: any, network: string, accounts: string[]) => {
const defaultConfig = {
owners: [accounts[0], accounts[1]],
confirmationsRequired: 2,
secondsRequired: 0,
};
const config = multiSigConfigByNetwork[network] || defaultConfig;
if (network !== 'live') {
deployer.deploy(MultiSigWalletWithTimeLock, config.owners, config.confirmationsRequired, config.secondsRequired)
.then(() => {
return deployer.deploy(TokenTransferProxy);
}).then(() => {
return deployer.deploy(TokenRegistry);
}).then(() => {
return deployer.deploy(EtherToken);
});
} else {
deployer.deploy([
[MultiSigWalletWithTimeLock, config.owners, config.confirmationsRequired, config.secondsRequired],
TokenTransferProxy,
TokenRegistry,
]);
}
};

View File

@@ -0,0 +1,86 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import {Artifacts} from '../util/artifacts';
import {constants} from '../util/constants';
import {ContractInstance, Token, TokenInfoByNetwork} from '../util/types';
import {tokenInfo} from './config/token_info';
const {
DummyToken,
EtherToken,
ZRXToken,
TokenRegistry,
} = new Artifacts(artifacts);
module.exports = (deployer: any, network: string) => {
const tokens = network === 'live' ? tokenInfo.live : tokenInfo.development;
deployer.then(() => {
return TokenRegistry.deployed();
}).then((tokenRegistry: ContractInstance) => {
if (network !== 'live') {
const totalSupply = Math.pow(10, 18) * 1000000000;
return Bluebird.each(tokens.map((token: Token) => DummyToken.new(
token.name,
token.symbol,
token.decimals,
totalSupply,
)), _.noop).then((dummyTokens: ContractInstance[]) => {
const weth = {
address: EtherToken.address,
name: 'Ether Token',
symbol: 'WETH',
url: '',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
};
return Bluebird.each(dummyTokens.map((tokenContract: ContractInstance, i: number) => {
const token = tokens[i];
return tokenRegistry.addToken(
tokenContract.address,
token.name,
token.symbol,
token.decimals,
token.ipfsHash,
token.swarmHash,
);
}).concat(tokenRegistry.addToken(
weth.address,
weth.name,
weth.symbol,
weth.decimals,
weth.ipfsHash,
weth.swarmHash,
)), _.noop);
});
} else {
const zrx = {
address: ZRXToken.address,
name: '0x Protocol Token',
symbol: 'ZRX',
url: 'https://www.0xproject.com/',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
};
return Bluebird.each(tokens.map((token: Token) => {
return tokenRegistry.addToken(
token.address,
token.name,
token.symbol,
token.decimals,
token.ipfsHash,
token.swarmHash,
);
}).concat(tokenRegistry.addToken(
zrx.address,
zrx.name,
zrx.symbol,
zrx.decimals,
zrx.ipfsHash,
zrx.swarmHash,
)), _.noop);
}
});
};

View File

@@ -0,0 +1,27 @@
import {Artifacts} from '../util/artifacts';
import {ContractInstance} from '../util/types';
const {
TokenTransferProxy,
Exchange,
TokenRegistry,
} = new Artifacts(artifacts);
let tokenTransferProxy: ContractInstance;
module.exports = (deployer: any) => {
deployer.then(async () => {
return Promise.all([
TokenTransferProxy.deployed(),
TokenRegistry.deployed(),
]);
})
.then((instances: ContractInstance[]) => {
let tokenRegistry: ContractInstance;
[tokenTransferProxy, tokenRegistry] = instances;
return tokenRegistry.getTokenAddressBySymbol('ZRX');
})
.then((ptAddress: string) => {
return deployer.deploy(Exchange, ptAddress, tokenTransferProxy.address);
}).then(() => {
return tokenTransferProxy.addAuthorizedAddress(Exchange.address);
});
};

View File

@@ -0,0 +1,25 @@
import {Artifacts} from '../util/artifacts';
import {ContractInstance} from '../util/types';
const {
TokenTransferProxy,
MultiSigWalletWithTimeLock,
TokenRegistry,
} = new Artifacts(artifacts);
let tokenRegistry: ContractInstance;
module.exports = (deployer: any, network: string) => {
if (network !== 'development') {
deployer.then(async () => {
return Promise.all([
TokenTransferProxy.deployed(),
TokenRegistry.deployed(),
]).then((instances: ContractInstance[]) => {
let tokenTransferProxy: ContractInstance;
[tokenTransferProxy, tokenRegistry] = instances;
return tokenTransferProxy.transferOwnership(MultiSigWalletWithTimeLock.address);
}).then(() => {
return tokenRegistry.transferOwnership(MultiSigWalletWithTimeLock.address);
});
});
}
};

View File

@@ -0,0 +1,10 @@
import {MultiSigConfigByNetwork} from '../../util/types';
// Make a copy of this file named `multisig.js` and input custom params as needed
export const multiSig: MultiSigConfigByNetwork = {
kovan: {
owners: [],
confirmationsRequired: 0,
secondsRequired: 0,
},
};

View File

@@ -0,0 +1,99 @@
import {constants} from '../../util/constants';
import {TokenInfoByNetwork} from '../../util/types';
export const tokenInfo: TokenInfoByNetwork = {
development: [
{
name: '0x Protocol Token',
symbol: 'ZRX',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
name: 'Augur Reputation Token',
symbol: 'REP',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
name: 'Digix DAO Token',
symbol: 'DGD',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
name: 'Golem Network Token',
symbol: 'GNT',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
name: 'MakerDAO',
symbol: 'MKR',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
name: 'Melon Token',
symbol: 'MLN',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
],
live: [
{
address: '0xecf8f87f810ecf450940c9f60066b4a7a501d6a7',
name: 'ETH Wrapper Token',
symbol: 'WETH',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
address: '0x48c80f1f4d53d5951e5d5438b54cba84f29f32a5',
name: 'Augur Reputation Token',
symbol: 'REP',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
address: '0xe0b7927c4af23765cb51314a0e0521a9645f0e2a',
name: 'Digix DAO Token',
symbol: 'DGD',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
address: '0xa74476443119a942de498590fe1f2454d7d4ac0d',
name: 'Golem Network Token',
symbol: 'GNT',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
address: '0xc66ea802717bfb9833400264dd12c2bceaa34a6d',
name: 'MakerDAO',
symbol: 'MKR',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
{
address: '0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1',
name: 'Melon Token',
symbol: 'MLN',
decimals: 18,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
},
],
};

View File

@@ -0,0 +1,71 @@
{
"name": "0x-smart-contracts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"transpile": "rm -rf ./transpiled; copyfiles ./build/**/* ./deploy/solc/solc_bin/* ./deploy/test/fixtures/contracts/**/* ./deploy/test/fixtures/contracts/* ./transpiled; tsc;",
"test": "npm run transpile; truffle test",
"compile": "npm run transpile; node transpiled/deploy/cli.js compile",
"migrate": "npm run transpile; truffle migrate",
"migrateCustom": "npm run transpile; node transpiled/deploy/cli.js migrate",
"deploy": "npm run migrate --network kovan",
"lint": "tslint --project . 'migrations/*.ts' 'test/**/*.ts' 'util/*.ts' 'deploy/**/*.ts'",
"testrpc": "testrpc --networkId 50",
"mocha": "npm run transpile; mocha transpiled/deploy/test/*_test.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/0xProject/0x-Smart-Contracts.git"
},
"author": "abandeali1@gmail.com",
"license": "ISC",
"bugs": {
"url": "https://github.com/0xProject/0x-Smart-Contracts/issues"
},
"homepage": "https://github.com/0xProject/0x-Smart-Contracts#readme",
"devDependencies": {
"@types/bluebird": "^3.5.3",
"@types/isomorphic-fetch": "^0.0.34",
"@types/lodash": "^4.14.63",
"@types/node": "^8.0.51",
"@types/request-promise-native": "^1.0.2",
"@types/yargs": "^8.0.2",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chai-as-promised-typescript-typings": "^0.0.3",
"chai-bignumber": "^2.0.1",
"chai-typescript-typings": "^0.0.0",
"copyfiles": "^1.2.0",
"dirty-chai": "^2.0.1",
"ethereumjs-testrpc": "4.0.1",
"mocha": "^4.0.1",
"solc": "^0.4.18",
"truffle": "3.4.3",
"tslint": "5.8.0",
"tslint-config-0xproject": "^0.0.2",
"types-bn": "^0.0.1",
"types-ethereumjs-util": "machinomy/types-ethereumjs-util",
"typescript": "^2.6.1",
"web3-typescript-typings": "^0.7.1",
"yargs": "^10.0.3"
},
"dependencies": {
"@0xproject/json-schemas": "^0.6.9",
"0x.js": "^0.22.6",
"bignumber.js": "^4.1.0",
"bluebird": "^3.5.0",
"bn.js": "^4.11.6",
"es6-promisify": "^5.0.0",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-util": "^5.1.1",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.17.4",
"request": "^2.81.0",
"web3": "0.20.2",
"web3-eth-abi": "^1.0.0-beta.24"
}
}

View File

@@ -0,0 +1,117 @@
import {ZeroEx, ZeroExError} from '0x.js';
import {BigNumber} from 'bignumber.js';
import * as chai from 'chai';
import promisify = require('es6-promisify');
import Web3 = require('web3');
import {Artifacts} from '../../util/artifacts';
import {chaiSetup} from './utils/chai_setup';
const {EtherToken} = new Artifacts(artifacts);
chaiSetup.configure();
const expect = chai.expect;
// In order to benefit from type-safety, we re-assign the global web3 instance injected by Truffle
// with type `any` to a variable of type `Web3`.
const web3: Web3 = (global as any).web3;
contract('EtherToken', (accounts: string[]) => {
const account = accounts[0];
const gasPrice = ZeroEx.toBaseUnitAmount(new BigNumber(20), 9);
let zeroEx: ZeroEx;
let etherTokenAddress: string;
before(async () => {
etherTokenAddress = EtherToken.address;
zeroEx = new ZeroEx(web3.currentProvider, {
gasPrice,
etherTokenContractAddress: etherTokenAddress,
});
});
const sendTransactionAsync = promisify(web3.eth.sendTransaction);
const getEthBalanceAsync = async (owner: string) => {
const balanceStr = await promisify(web3.eth.getBalance)(owner);
const balance = new BigNumber(balanceStr);
return balance;
};
describe('deposit', () => {
it('should throw if caller attempts to deposit more Ether than caller balance', async () => {
const initEthBalance = await getEthBalanceAsync(account);
const ethToDeposit = initEthBalance.plus(1);
return expect(zeroEx.etherToken.depositAsync(ethToDeposit, account))
.to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit);
});
it('should convert deposited Ether to wrapped Ether tokens', async () => {
const initEthBalance = await getEthBalanceAsync(account);
const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
const ethToDeposit = new BigNumber(web3.toWei(1, 'ether'));
const txHash = await zeroEx.etherToken.depositAsync(ethToDeposit, account);
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);
const ethSpentOnGas = gasPrice.times(receipt.gasUsed);
const finalEthBalance = await getEthBalanceAsync(account);
const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas)));
expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit));
});
});
describe('withdraw', () => {
it('should throw if caller attempts to withdraw greater than caller balance', async () => {
const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
const ethTokensToWithdraw = initEthTokenBalance.plus(1);
return expect(zeroEx.etherToken.withdrawAsync(ethTokensToWithdraw, account))
.to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal);
});
it('should convert ether tokens to ether with sufficient balance', async () => {
const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
const initEthBalance = await getEthBalanceAsync(account);
const ethTokensToWithdraw = initEthTokenBalance;
expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0);
const txHash = await zeroEx.etherToken.withdrawAsync(ethTokensToWithdraw, account);
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);
const ethSpentOnGas = gasPrice.times(receipt.gasUsed);
const finalEthBalance = await getEthBalanceAsync(account);
const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.plus(ethTokensToWithdraw.minus(ethSpentOnGas)));
expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.minus(ethTokensToWithdraw));
});
});
describe('fallback', () => {
it('should convert sent ether to ether tokens', async () => {
const initEthBalance = await getEthBalanceAsync(account);
const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
const ethToDeposit = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
const txHash = await sendTransactionAsync({
from: account,
to: etherTokenAddress,
value: ethToDeposit,
gasPrice,
});
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);
const ethSpentOnGas = gasPrice.times(receipt.gasUsed);
const finalEthBalance = await getEthBalanceAsync(account);
const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas)));
expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit));
});
});
});

View File

@@ -0,0 +1,744 @@
import {ZeroEx} from '0x.js';
import {BigNumber} from 'bignumber.js';
import * as chai from 'chai';
import ethUtil = require('ethereumjs-util');
import * as Web3 from 'web3';
import {Artifacts} from '../../../util/artifacts';
import {Balances} from '../../../util/balances';
import {constants} from '../../../util/constants';
import {crypto} from '../../../util/crypto';
import {ExchangeWrapper} from '../../../util/exchange_wrapper';
import {Order} from '../../../util/order';
import {OrderFactory} from '../../../util/order_factory';
import {BalancesByOwner, ContractInstance, ExchangeContractErrs} from '../../../util/types';
import {chaiSetup} from '../utils/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
const {
Exchange,
TokenTransferProxy,
DummyToken,
TokenRegistry,
MaliciousToken,
} = new Artifacts(artifacts);
// In order to benefit from type-safety, we re-assign the global web3 instance injected by Truffle
// with type `any` to a variable of type `Web3`.
const web3: Web3 = (global as any).web3;
contract('Exchange', (accounts: string[]) => {
const maker = accounts[0];
const tokenOwner = accounts[0];
const taker = accounts[1] || accounts[accounts.length - 1];
const feeRecipient = accounts[2] || accounts[accounts.length - 1];
const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
let rep: ContractInstance;
let dgd: ContractInstance;
let zrx: ContractInstance;
let exchange: ContractInstance;
let tokenRegistry: ContractInstance;
let order: Order;
let balances: BalancesByOwner;
let exWrapper: ExchangeWrapper;
let dmyBalances: Balances;
let orderFactory: OrderFactory;
let zeroEx: ZeroEx;
before(async () => {
[tokenRegistry, exchange] = await Promise.all([
TokenRegistry.deployed(),
Exchange.deployed(),
]);
exWrapper = new ExchangeWrapper(exchange);
zeroEx = new ZeroEx(web3.currentProvider, {
exchangeContractAddress: exchange.address,
});
const [repAddress, dgdAddress, zrxAddress] = await Promise.all([
tokenRegistry.getTokenAddressBySymbol('REP'),
tokenRegistry.getTokenAddressBySymbol('DGD'),
tokenRegistry.getTokenAddressBySymbol('ZRX'),
]);
const defaultOrderParams = {
exchangeContractAddress: Exchange.address,
maker,
feeRecipient,
makerToken: repAddress,
takerToken: dgdAddress,
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
};
orderFactory = new OrderFactory(defaultOrderParams);
[rep, dgd, zrx] = await Promise.all([
DummyToken.at(repAddress),
DummyToken.at(dgdAddress),
DummyToken.at(zrxAddress),
]);
dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]);
await Promise.all([
rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: maker}),
rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: taker}),
rep.setBalance(maker, INITIAL_BALANCE, {from: tokenOwner}),
rep.setBalance(taker, INITIAL_BALANCE, {from: tokenOwner}),
dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: maker}),
dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: taker}),
dgd.setBalance(maker, INITIAL_BALANCE, {from: tokenOwner}),
dgd.setBalance(taker, INITIAL_BALANCE, {from: tokenOwner}),
zrx.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: maker}),
zrx.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: taker}),
zrx.setBalance(maker, INITIAL_BALANCE, {from: tokenOwner}),
zrx.setBalance(taker, INITIAL_BALANCE, {from: tokenOwner}),
]);
});
describe('internal functions', () => {
it('should include transferViaTokenTransferProxy', () => {
expect(exchange.transferViaTokenTransferProxy).to.be.undefined();
});
it('should include isTransferable', () => {
expect(exchange.isTransferable).to.be.undefined();
});
it('should include getBalance', () => {
expect(exchange.getBalance).to.be.undefined();
});
it('should include getAllowance', () => {
expect(exchange.getAllowance).to.be.undefined();
});
});
describe('fillOrder', () => {
beforeEach(async () => {
balances = await dmyBalances.getAsync();
order = await orderFactory.newSignedOrderAsync();
});
it('should create an unfillable order', async () => {
order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: new BigNumber(1001),
takerTokenAmount: new BigNumber(3),
});
const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
const fillTakerTokenAmount1 = new BigNumber(2);
await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount: fillTakerTokenAmount1});
const filledTakerTokenAmountAfter1 = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountAfter1).to.be.bignumber.equal(fillTakerTokenAmount1);
const fillTakerTokenAmount2 = new BigNumber(1);
await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount: fillTakerTokenAmount2});
const filledTakerTokenAmountAfter2 = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountAfter2).to.be.bignumber.equal(filledTakerTokenAmountAfter1);
});
it('should transfer the correct amounts when makerTokenAmount === takerTokenAmount', async () => {
order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
});
const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
const fillTakerTokenAmount = order.params.takerTokenAmount.div(2);
await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount});
const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount);
const newBalances = await dmyBalances.getAsync();
const fillMakerTokenAmount = fillTakerTokenAmount
.times(order.params.makerTokenAmount)
.dividedToIntegerBy(order.params.takerTokenAmount);
const paidMakerFee = order.params.makerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
const paidTakerFee = order.params.takerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
expect(newBalances[maker][order.params.makerToken])
.to.be.bignumber.equal(balances[maker][order.params.makerToken].minus(fillMakerTokenAmount));
expect(newBalances[maker][order.params.takerToken])
.to.be.bignumber.equal(balances[maker][order.params.takerToken].add(fillTakerTokenAmount));
expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(balances[maker][zrx.address].minus(paidMakerFee));
expect(newBalances[taker][order.params.takerToken])
.to.be.bignumber.equal(balances[taker][order.params.takerToken].minus(fillTakerTokenAmount));
expect(newBalances[taker][order.params.makerToken])
.to.be.bignumber.equal(balances[taker][order.params.makerToken].add(fillMakerTokenAmount));
expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(paidTakerFee));
expect(newBalances[feeRecipient][zrx.address])
.to.be.bignumber.equal(balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)));
});
it('should transfer the correct amounts when makerTokenAmount > takerTokenAmount', async () => {
order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
});
const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
const fillTakerTokenAmount = order.params.takerTokenAmount.div(2);
await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount});
const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount);
const newBalances = await dmyBalances.getAsync();
const fillMakerTokenAmount = fillTakerTokenAmount
.times(order.params.makerTokenAmount)
.dividedToIntegerBy(order.params.takerTokenAmount);
const paidMakerFee = order.params.makerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
const paidTakerFee = order.params.takerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
expect(newBalances[maker][order.params.makerToken])
.to.be.bignumber.equal(balances[maker][order.params.makerToken].minus(fillMakerTokenAmount));
expect(newBalances[maker][order.params.takerToken])
.to.be.bignumber.equal(balances[maker][order.params.takerToken].add(fillTakerTokenAmount));
expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(balances[maker][zrx.address].minus(paidMakerFee));
expect(newBalances[taker][order.params.takerToken])
.to.be.bignumber.equal(balances[taker][order.params.takerToken].minus(fillTakerTokenAmount));
expect(newBalances[taker][order.params.makerToken])
.to.be.bignumber.equal(balances[taker][order.params.makerToken].add(fillMakerTokenAmount));
expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(paidTakerFee));
expect(newBalances[feeRecipient][zrx.address])
.to.be.bignumber.equal(balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)));
});
it('should transfer the correct amounts when makerTokenAmount < takerTokenAmount', async () => {
order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
});
const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
const fillTakerTokenAmount = order.params.takerTokenAmount.div(2);
await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount});
const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount);
const newBalances = await dmyBalances.getAsync();
const fillMakerTokenAmount = fillTakerTokenAmount
.times(order.params.makerTokenAmount)
.dividedToIntegerBy(order.params.takerTokenAmount);
const paidMakerFee = order.params.makerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
const paidTakerFee = order.params.takerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
expect(newBalances[maker][order.params.makerToken])
.to.be.bignumber.equal(balances[maker][order.params.makerToken].minus(fillMakerTokenAmount));
expect(newBalances[maker][order.params.takerToken])
.to.be.bignumber.equal(balances[maker][order.params.takerToken].add(fillTakerTokenAmount));
expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(balances[maker][zrx.address].minus(paidMakerFee));
expect(newBalances[taker][order.params.takerToken])
.to.be.bignumber.equal(balances[taker][order.params.takerToken].minus(fillTakerTokenAmount));
expect(newBalances[taker][order.params.makerToken])
.to.be.bignumber.equal(balances[taker][order.params.makerToken].add(fillMakerTokenAmount));
expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(paidTakerFee));
expect(newBalances[feeRecipient][zrx.address])
.to.be.bignumber.equal(balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)));
});
it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => {
order = await orderFactory.newSignedOrderAsync({
taker,
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
});
const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
const fillTakerTokenAmount = order.params.takerTokenAmount.div(2);
await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount});
const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(order.params.orderHashHex);
const expectedFillAmountTAfter = fillTakerTokenAmount.add(filledTakerTokenAmountBefore);
expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(expectedFillAmountTAfter);
const newBalances = await dmyBalances.getAsync();
const fillMakerTokenAmount = fillTakerTokenAmount
.times(order.params.makerTokenAmount)
.dividedToIntegerBy(order.params.takerTokenAmount);
const paidMakerFee = order.params.makerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
const paidTakerFee = order.params.takerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
expect(newBalances[maker][order.params.makerToken])
.to.be.bignumber.equal(balances[maker][order.params.makerToken].minus(fillMakerTokenAmount));
expect(newBalances[maker][order.params.takerToken])
.to.be.bignumber.equal(balances[maker][order.params.takerToken].add(fillTakerTokenAmount));
expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(balances[maker][zrx.address].minus(paidMakerFee));
expect(newBalances[taker][order.params.takerToken])
.to.be.bignumber.equal(balances[taker][order.params.takerToken].minus(fillTakerTokenAmount));
expect(newBalances[taker][order.params.makerToken])
.to.be.bignumber.equal(balances[taker][order.params.makerToken].add(fillMakerTokenAmount));
expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(paidTakerFee));
expect(newBalances[feeRecipient][zrx.address])
.to.be.bignumber.equal(balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)));
});
it('should fill remaining value if fillTakerTokenAmount > remaining takerTokenAmount', async () => {
const fillTakerTokenAmount = order.params.takerTokenAmount.div(2);
await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount});
const res = await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount: order.params.takerTokenAmount});
expect(res.logs[0].args.filledTakerTokenAmount)
.to.be.bignumber.equal(order.params.takerTokenAmount.minus(fillTakerTokenAmount));
const newBalances = await dmyBalances.getAsync();
expect(newBalances[maker][order.params.makerToken])
.to.be.bignumber.equal(balances[maker][order.params.makerToken].minus(order.params.makerTokenAmount));
expect(newBalances[maker][order.params.takerToken])
.to.be.bignumber.equal(balances[maker][order.params.takerToken].add(order.params.takerTokenAmount));
expect(newBalances[maker][zrx.address])
.to.be.bignumber.equal(balances[maker][zrx.address].minus(order.params.makerFee));
expect(newBalances[taker][order.params.takerToken])
.to.be.bignumber.equal(balances[taker][order.params.takerToken].minus(order.params.takerTokenAmount));
expect(newBalances[taker][order.params.makerToken])
.to.be.bignumber.equal(balances[taker][order.params.makerToken].add(order.params.makerTokenAmount));
expect(newBalances[taker][zrx.address])
.to.be.bignumber.equal(balances[taker][zrx.address].minus(order.params.takerFee));
expect(newBalances[feeRecipient][zrx.address])
.to.be.bignumber.equal(
balances[feeRecipient][zrx.address].add(order.params.makerFee.add(order.params.takerFee)),
);
});
it('should log 1 event with the correct arguments when order has a feeRecipient', async () => {
const divisor = 2;
const res = await exWrapper.fillOrderAsync(order, taker,
{fillTakerTokenAmount: order.params.takerTokenAmount.div(divisor)});
expect(res.logs).to.have.length(1);
const logArgs = res.logs[0].args;
const expectedFilledMakerTokenAmount = order.params.makerTokenAmount.div(divisor);
const expectedFilledTakerTokenAmount = order.params.takerTokenAmount.div(divisor);
const expectedFeeMPaid = order.params.makerFee.div(divisor);
const expectedFeeTPaid = order.params.takerFee.div(divisor);
const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]);
const expectedTokens = ethUtil.bufferToHex(tokensHashBuff);
expect(order.params.maker).to.be.equal(logArgs.maker);
expect(taker).to.be.equal(logArgs.taker);
expect(order.params.feeRecipient).to.be.equal(logArgs.feeRecipient);
expect(order.params.makerToken).to.be.equal(logArgs.makerToken);
expect(order.params.takerToken).to.be.equal(logArgs.takerToken);
expect(expectedFilledMakerTokenAmount).to.be.bignumber.equal(logArgs.filledMakerTokenAmount);
expect(expectedFilledTakerTokenAmount).to.be.bignumber.equal(logArgs.filledTakerTokenAmount);
expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.paidMakerFee);
expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.paidTakerFee);
expect(expectedTokens).to.be.equal(logArgs.tokens);
expect(order.params.orderHashHex).to.be.equal(logArgs.orderHash);
});
it('should log 1 event with the correct arguments when order has no feeRecipient', async () => {
order = await orderFactory.newSignedOrderAsync({
feeRecipient: ZeroEx.NULL_ADDRESS,
});
const divisor = 2;
const res = await exWrapper.fillOrderAsync(order, taker,
{fillTakerTokenAmount: order.params.takerTokenAmount.div(divisor)});
expect(res.logs).to.have.length(1);
const logArgs = res.logs[0].args;
const expectedFilledMakerTokenAmount = order.params.makerTokenAmount.div(divisor);
const expectedFilledTakerTokenAmount = order.params.takerTokenAmount.div(divisor);
const expectedFeeMPaid = new BigNumber(0);
const expectedFeeTPaid = new BigNumber(0);
const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]);
const expectedTokens = ethUtil.bufferToHex(tokensHashBuff);
expect(order.params.maker).to.be.equal(logArgs.maker);
expect(taker).to.be.equal(logArgs.taker);
expect(order.params.feeRecipient).to.be.equal(logArgs.feeRecipient);
expect(order.params.makerToken).to.be.equal(logArgs.makerToken);
expect(order.params.takerToken).to.be.equal(logArgs.takerToken);
expect(expectedFilledMakerTokenAmount).to.be.bignumber.equal(logArgs.filledMakerTokenAmount);
expect(expectedFilledTakerTokenAmount).to.be.bignumber.equal(logArgs.filledTakerTokenAmount);
expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.paidMakerFee);
expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.paidTakerFee);
expect(expectedTokens).to.be.equal(logArgs.tokens);
expect(order.params.orderHashHex).to.be.equal(logArgs.orderHash);
});
it('should throw when taker is specified and order is claimed by other', async () => {
order = await orderFactory.newSignedOrderAsync({
taker: feeRecipient,
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
});
return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if signature is invalid', async () => {
order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
});
order.params.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR'));
order.params.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS'));
return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if makerTokenAmount is 0', async () => {
order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: new BigNumber(0),
});
return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if takerTokenAmount is 0', async () => {
order = await orderFactory.newSignedOrderAsync({
takerTokenAmount: new BigNumber(0),
});
return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if fillTakerTokenAmount is 0', async () => {
order = await orderFactory.newSignedOrderAsync();
return expect(exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount: new BigNumber(0)}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should not change balances if maker balances are too low to fill order and \
shouldThrowOnInsufficientBalanceOrAllowance = false',
async () => {
order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
});
await exWrapper.fillOrderAsync(order, taker);
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should throw if maker balances are too low to fill order and \
shouldThrowOnInsufficientBalanceOrAllowance = true',
async () => {
order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
});
return expect(exWrapper.fillOrderAsync(order, taker, {shouldThrowOnInsufficientBalanceOrAllowance: true}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should not change balances if taker balances are too low to fill order and \
shouldThrowOnInsufficientBalanceOrAllowance = false',
async () => {
order = await orderFactory.newSignedOrderAsync({
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
});
await exWrapper.fillOrderAsync(order, taker);
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should throw if taker balances are too low to fill order and \
shouldThrowOnInsufficientBalanceOrAllowance = true',
async () => {
order = await orderFactory.newSignedOrderAsync({
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
});
return expect(exWrapper.fillOrderAsync(order, taker, {shouldThrowOnInsufficientBalanceOrAllowance: true}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should not change balances if maker allowances are too low to fill order and \
shouldThrowOnInsufficientBalanceOrAllowance = false',
async () => {
await rep.approve(TokenTransferProxy.address, 0, {from: maker});
await exWrapper.fillOrderAsync(order, taker);
await rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: maker});
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should throw if maker allowances are too low to fill order and \
shouldThrowOnInsufficientBalanceOrAllowance = true',
async () => {
await rep.approve(TokenTransferProxy.address, 0, {from: maker});
expect(exWrapper.fillOrderAsync(order, taker, {shouldThrowOnInsufficientBalanceOrAllowance: true}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
await rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: maker});
});
it('should not change balances if taker allowances are too low to fill order and \
shouldThrowOnInsufficientBalanceOrAllowance = false',
async () => {
await dgd.approve(TokenTransferProxy.address, 0, {from: taker});
await exWrapper.fillOrderAsync(order, taker);
await dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: taker});
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should throw if taker allowances are too low to fill order and \
shouldThrowOnInsufficientBalanceOrAllowance = true',
async () => {
await dgd.approve(TokenTransferProxy.address, 0, {from: taker});
expect(exWrapper.fillOrderAsync(order, taker, {shouldThrowOnInsufficientBalanceOrAllowance: true}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
await dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: taker});
});
it('should not change balances if makerToken is ZRX, makerTokenAmount + makerFee > maker balance, \
and shouldThrowOnInsufficientBalanceOrAllowance = false',
async () => {
const makerZRXBalance = new BigNumber(balances[maker][zrx.address]);
order = await orderFactory.newSignedOrderAsync({
makerToken: zrx.address,
makerTokenAmount: makerZRXBalance,
makerFee: new BigNumber(1),
});
await exWrapper.fillOrderAsync(order, taker);
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should not change balances if makerToken is ZRX, makerTokenAmount + makerFee > maker allowance, \
and shouldThrowOnInsufficientBalanceOrAllowance = false',
async () => {
const makerZRXAllowance = await zrx.allowance(maker, TokenTransferProxy.address);
order = await orderFactory.newSignedOrderAsync({
makerToken: zrx.address,
makerTokenAmount: new BigNumber(makerZRXAllowance),
makerFee: new BigNumber(1),
});
await exWrapper.fillOrderAsync(order, taker);
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should not change balances if takerToken is ZRX, takerTokenAmount + takerFee > taker balance, \
and shouldThrowOnInsufficientBalanceOrAllowance = false',
async () => {
const takerZRXBalance = new BigNumber(balances[taker][zrx.address]);
order = await orderFactory.newSignedOrderAsync({
takerToken: zrx.address,
takerTokenAmount: takerZRXBalance,
takerFee: new BigNumber(1),
});
await exWrapper.fillOrderAsync(order, taker);
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should not change balances if takerToken is ZRX, takerTokenAmount + takerFee > taker allowance, \
and shouldThrowOnInsufficientBalanceOrAllowance = false',
async () => {
const takerZRXAllowance = await zrx.allowance(taker, TokenTransferProxy.address);
order = await orderFactory.newSignedOrderAsync({
takerToken: zrx.address,
takerTokenAmount: new BigNumber(takerZRXAllowance),
takerFee: new BigNumber(1),
});
await exWrapper.fillOrderAsync(order, taker);
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should throw if getBalance or getAllowance attempts to change state and \
shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
const maliciousToken = await MaliciousToken.new();
await maliciousToken.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, {from: taker});
order = await orderFactory.newSignedOrderAsync({
takerToken: maliciousToken.address,
});
return expect(exWrapper.fillOrderAsync(order, taker, {shouldThrowOnInsufficientBalanceOrAllowance: false}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should not change balances if an order is expired', async () => {
order = await orderFactory.newSignedOrderAsync({
expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
await exWrapper.fillOrderAsync(order, taker);
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should log an error event if an order is expired', async () => {
order = await orderFactory.newSignedOrderAsync({
expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
const res = await exWrapper.fillOrderAsync(order, taker);
expect(res.logs).to.have.length(1);
const errCode = res.logs[0].args.errorId.toNumber();
expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED);
});
it('should log an error event if no value is filled', async () => {
await exWrapper.fillOrderAsync(order, taker);
const res = await exWrapper.fillOrderAsync(order, taker);
expect(res.logs).to.have.length(1);
const errCode = res.logs[0].args.errorId.toNumber();
expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED);
});
});
describe('cancelOrder', () => {
beforeEach(async () => {
balances = await dmyBalances.getAsync();
order = await orderFactory.newSignedOrderAsync();
});
it('should throw if not sent by maker', async () => {
return expect(exWrapper.cancelOrderAsync(order, taker)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if makerTokenAmount is 0', async () => {
order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: new BigNumber(0),
});
return expect(exWrapper.cancelOrderAsync(order, maker)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if takerTokenAmount is 0', async () => {
order = await orderFactory.newSignedOrderAsync({
takerTokenAmount: new BigNumber(0),
});
return expect(exWrapper.cancelOrderAsync(order, maker)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if cancelTakerTokenAmount is 0', async () => {
order = await orderFactory.newSignedOrderAsync();
return expect(exWrapper.cancelOrderAsync(order, maker, {cancelTakerTokenAmount: new BigNumber(0)}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should be able to cancel a full order', async () => {
await exWrapper.cancelOrderAsync(order, maker);
await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount: order.params.takerTokenAmount.div(2)});
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should be able to cancel part of an order', async () => {
const cancelTakerTokenAmount = order.params.takerTokenAmount.div(2);
await exWrapper.cancelOrderAsync(order, maker, {cancelTakerTokenAmount});
const res = await exWrapper.fillOrderAsync(order, taker, {fillTakerTokenAmount: order.params.takerTokenAmount});
expect(res.logs[0].args.filledTakerTokenAmount)
.to.be.bignumber.equal(order.params.takerTokenAmount.minus(cancelTakerTokenAmount));
const newBalances = await dmyBalances.getAsync();
const cancelMakerTokenAmount = cancelTakerTokenAmount
.times(order.params.makerTokenAmount)
.dividedToIntegerBy(order.params.takerTokenAmount);
const paidMakerFee = order.params.makerFee
.times(cancelMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
const paidTakerFee = order.params.takerFee
.times(cancelMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
expect(newBalances[maker][order.params.makerToken])
.to.be.bignumber.equal(balances[maker][order.params.makerToken].minus(cancelMakerTokenAmount));
expect(newBalances[maker][order.params.takerToken])
.to.be.bignumber.equal(balances[maker][order.params.takerToken].add(cancelTakerTokenAmount));
expect(newBalances[maker][zrx.address])
.to.be.bignumber.equal(balances[maker][zrx.address].minus(paidMakerFee));
expect(newBalances[taker][order.params.takerToken])
.to.be.bignumber.equal(balances[taker][order.params.takerToken].minus(cancelTakerTokenAmount));
expect(newBalances[taker][order.params.makerToken])
.to.be.bignumber.equal(balances[taker][order.params.makerToken].add(cancelMakerTokenAmount));
expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(paidTakerFee));
expect(newBalances[feeRecipient][zrx.address])
.to.be.bignumber.equal(balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)));
});
it('should log 1 event with correct arguments', async () => {
const divisor = 2;
const res = await exWrapper.cancelOrderAsync(order, maker,
{cancelTakerTokenAmount: order.params.takerTokenAmount.div(divisor)});
expect(res.logs).to.have.length(1);
const logArgs = res.logs[0].args;
const expectedCancelledMakerTokenAmount = order.params.makerTokenAmount.div(divisor);
const expectedCancelledTakerTokenAmount = order.params.takerTokenAmount.div(divisor);
const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]);
const expectedTokens = ethUtil.bufferToHex(tokensHashBuff);
expect(order.params.maker).to.be.equal(logArgs.maker);
expect(order.params.feeRecipient).to.be.equal(logArgs.feeRecipient);
expect(order.params.makerToken).to.be.equal(logArgs.makerToken);
expect(order.params.takerToken).to.be.equal(logArgs.takerToken);
expect(expectedCancelledMakerTokenAmount).to.be.bignumber.equal(logArgs.cancelledMakerTokenAmount);
expect(expectedCancelledTakerTokenAmount).to.be.bignumber.equal(logArgs.cancelledTakerTokenAmount);
expect(expectedTokens).to.be.equal(logArgs.tokens);
expect(order.params.orderHashHex).to.be.equal(logArgs.orderHash);
});
it('should not log events if no value is cancelled', async () => {
await exWrapper.cancelOrderAsync(order, maker);
const res = await exWrapper.cancelOrderAsync(order, maker);
expect(res.logs).to.have.length(1);
const errId = res.logs[0].args.errorId.toNumber();
const errCode = res.logs[0].args.errorId.toNumber();
expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED);
});
it('should not log events if order is expired', async () => {
order = await orderFactory.newSignedOrderAsync({
expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
const res = await exWrapper.cancelOrderAsync(order, maker);
expect(res.logs).to.have.length(1);
const errCode = res.logs[0].args.errorId.toNumber();
expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED);
});
});
});

View File

@@ -0,0 +1,173 @@
import {ZeroEx} from '0x.js';
import {BigNumber} from 'bignumber.js';
import * as chai from 'chai';
import ethUtil = require('ethereumjs-util');
import {Artifacts} from '../../../util/artifacts';
import {ExchangeWrapper} from '../../../util/exchange_wrapper';
import {Order} from '../../../util/order';
import {OrderFactory} from '../../../util/order_factory';
import {chaiSetup} from '../utils/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
const {
Exchange,
TokenRegistry,
} = new Artifacts(artifacts);
contract('Exchange', (accounts: string[]) => {
const maker = accounts[0];
const feeRecipient = accounts[1] || accounts[accounts.length - 1];
let order: Order;
let exchangeWrapper: ExchangeWrapper;
let orderFactory: OrderFactory;
before(async () => {
const [tokenRegistry, exchange] = await Promise.all([
TokenRegistry.deployed(),
Exchange.deployed(),
]);
exchangeWrapper = new ExchangeWrapper(exchange);
const [repAddress, dgdAddress] = await Promise.all([
tokenRegistry.getTokenAddressBySymbol('REP'),
tokenRegistry.getTokenAddressBySymbol('DGD'),
]);
const defaultOrderParams = {
exchangeContractAddress: Exchange.address,
maker,
feeRecipient,
makerToken: repAddress,
takerToken: dgdAddress,
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
};
orderFactory = new OrderFactory(defaultOrderParams);
});
beforeEach(async () => {
order = await orderFactory.newSignedOrderAsync();
});
describe('getOrderHash', () => {
it('should output the correct orderHash', async () => {
const orderHashHex = await exchangeWrapper.getOrderHashAsync(order);
expect(order.params.orderHashHex).to.be.equal(orderHashHex);
});
});
describe('isValidSignature', () => {
beforeEach(async () => {
order = await orderFactory.newSignedOrderAsync();
});
it('should return true with a valid signature', async () => {
const success = await exchangeWrapper.isValidSignatureAsync(order);
const isValidSignature = order.isValidSignature();
expect(isValidSignature).to.be.true();
expect(success).to.be.true();
});
it('should return false with an invalid signature', async () => {
order.params.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR'));
order.params.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS'));
const success = await exchangeWrapper.isValidSignatureAsync(order);
expect(order.isValidSignature()).to.be.false();
expect(success).to.be.false();
});
});
describe('isRoundingError', () => {
it('should return false if there is a rounding error of 0.1%', async () => {
const numerator = new BigNumber(20);
const denominator = new BigNumber(999);
const target = new BigNumber(50);
// rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1%
const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
expect(isRoundingError).to.be.false();
});
it('should return false if there is a rounding of 0.09%', async () => {
const numerator = new BigNumber(20);
const denominator = new BigNumber(9991);
const target = new BigNumber(500);
// rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09%
const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
expect(isRoundingError).to.be.false();
});
it('should return true if there is a rounding error of 0.11%', async () => {
const numerator = new BigNumber(20);
const denominator = new BigNumber(9989);
const target = new BigNumber(500);
// rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011%
const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
expect(isRoundingError).to.be.true();
});
it('should return true if there is a rounding error > 0.1%', async () => {
const numerator = new BigNumber(3);
const denominator = new BigNumber(7);
const target = new BigNumber(10);
// rounding error = ((3*10/7) - floor(3*10/7)) / (3*10/7) = 6.67%
const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
expect(isRoundingError).to.be.true();
});
it('should return false when there is no rounding error', async () => {
const numerator = new BigNumber(1);
const denominator = new BigNumber(2);
const target = new BigNumber(10);
const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
expect(isRoundingError).to.be.false();
});
it('should return false when there is rounding error <= 0.1%', async () => {
// randomly generated numbers
const numerator = new BigNumber(76564);
const denominator = new BigNumber(676373677);
const target = new BigNumber(105762562);
// rounding error = ((76564*105762562/676373677) - floor(76564*105762562/676373677)) /
// (76564*105762562/676373677) = 0.0007%
const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
expect(isRoundingError).to.be.false();
});
});
describe('getPartialAmount', () => {
it('should return the numerator/denominator*target', async () => {
const numerator = new BigNumber(1);
const denominator = new BigNumber(2);
const target = new BigNumber(10);
const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target);
const expectedPartialAmount = 5;
expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount);
});
it('should round down', async () => {
const numerator = new BigNumber(2);
const denominator = new BigNumber(3);
const target = new BigNumber(10);
const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target);
const expectedPartialAmount = 6;
expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount);
});
it('should round .5 down', async () => {
const numerator = new BigNumber(1);
const denominator = new BigNumber(20);
const target = new BigNumber(10);
const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target);
const expectedPartialAmount = 0;
expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount);
});
});
});

View File

@@ -0,0 +1,317 @@
import {ZeroEx} from '0x.js';
import {BigNumber} from 'bignumber.js';
import * as chai from 'chai';
import * as _ from 'lodash';
import {Artifacts} from '../../../util/artifacts';
import {Balances} from '../../../util/balances';
import {constants} from '../../../util/constants';
import {ExchangeWrapper} from '../../../util/exchange_wrapper';
import {Order} from '../../../util/order';
import {OrderFactory} from '../../../util/order_factory';
import {BalancesByOwner, ContractInstance} from '../../../util/types';
import {chaiSetup} from '../utils/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
const {
Exchange,
TokenTransferProxy,
DummyToken,
TokenRegistry,
} = new Artifacts(artifacts);
contract('Exchange', (accounts: string[]) => {
const maker = accounts[0];
const tokenOwner = accounts[0];
const taker = accounts[1] || accounts[accounts.length - 1];
const feeRecipient = accounts[2] || accounts[accounts.length - 1];
const INIT_BAL = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
const INIT_ALLOW = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
let rep: ContractInstance;
let dgd: ContractInstance;
let zrx: ContractInstance;
let exchange: ContractInstance;
let tokenRegistry: ContractInstance;
let balances: BalancesByOwner;
let exWrapper: ExchangeWrapper;
let dmyBalances: Balances;
let orderFactory: OrderFactory;
before(async () => {
[tokenRegistry, exchange] = await Promise.all([
TokenRegistry.deployed(),
Exchange.deployed(),
]);
exWrapper = new ExchangeWrapper(exchange);
const [repAddress, dgdAddress, zrxAddress] = await Promise.all([
tokenRegistry.getTokenAddressBySymbol('REP'),
tokenRegistry.getTokenAddressBySymbol('DGD'),
tokenRegistry.getTokenAddressBySymbol('ZRX'),
]);
const defaultOrderParams = {
exchangeContractAddress: Exchange.address,
maker,
feeRecipient,
makerToken: repAddress,
takerToken: dgdAddress,
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
};
orderFactory = new OrderFactory(defaultOrderParams);
[rep, dgd, zrx] = await Promise.all([
DummyToken.at(repAddress),
DummyToken.at(dgdAddress),
DummyToken.at(zrxAddress),
]);
dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]);
await Promise.all([
rep.approve(TokenTransferProxy.address, INIT_ALLOW, {from: maker}),
rep.approve(TokenTransferProxy.address, INIT_ALLOW, {from: taker}),
rep.setBalance(maker, INIT_BAL, {from: tokenOwner}),
rep.setBalance(taker, INIT_BAL, {from: tokenOwner}),
dgd.approve(TokenTransferProxy.address, INIT_ALLOW, {from: maker}),
dgd.approve(TokenTransferProxy.address, INIT_ALLOW, {from: taker}),
dgd.setBalance(maker, INIT_BAL, {from: tokenOwner}),
dgd.setBalance(taker, INIT_BAL, {from: tokenOwner}),
zrx.approve(TokenTransferProxy.address, INIT_ALLOW, {from: maker}),
zrx.approve(TokenTransferProxy.address, INIT_ALLOW, {from: taker}),
zrx.setBalance(maker, INIT_BAL, {from: tokenOwner}),
zrx.setBalance(taker, INIT_BAL, {from: tokenOwner}),
]);
});
describe('fillOrKillOrder', () => {
beforeEach(async () => {
balances = await dmyBalances.getAsync();
});
it('should transfer the correct amounts', async () => {
const order = await orderFactory.newSignedOrderAsync({
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
});
const fillTakerTokenAmount = order.params.takerTokenAmount.div(2);
await exWrapper.fillOrKillOrderAsync(order, taker, {fillTakerTokenAmount});
const newBalances = await dmyBalances.getAsync();
const fillMakerTokenAmount = fillTakerTokenAmount
.times(order.params.makerTokenAmount)
.dividedToIntegerBy(order.params.takerTokenAmount);
const makerFee = order.params.makerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
const takerFee = order.params.takerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
expect(newBalances[maker][order.params.makerToken])
.to.be.bignumber.equal(balances[maker][order.params.makerToken].minus(fillMakerTokenAmount));
expect(newBalances[maker][order.params.takerToken])
.to.be.bignumber.equal(balances[maker][order.params.takerToken].add(fillTakerTokenAmount));
expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(balances[maker][zrx.address].minus(makerFee));
expect(newBalances[taker][order.params.takerToken])
.to.be.bignumber.equal(balances[taker][order.params.takerToken].minus(fillTakerTokenAmount));
expect(newBalances[taker][order.params.makerToken])
.to.be.bignumber.equal(balances[taker][order.params.makerToken].add(fillMakerTokenAmount));
expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(takerFee));
expect(newBalances[feeRecipient][zrx.address])
.to.be.bignumber.equal(balances[feeRecipient][zrx.address].add(makerFee.add(takerFee)));
});
it('should throw if an order is expired', async () => {
const order = await orderFactory.newSignedOrderAsync({
expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
return expect(exWrapper.fillOrKillOrderAsync(order, taker))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if entire fillTakerTokenAmount not filled', async () => {
const order = await orderFactory.newSignedOrderAsync();
const from = taker;
await exWrapper.fillOrderAsync(order, from, {fillTakerTokenAmount: order.params.takerTokenAmount.div(2)});
return expect(exWrapper.fillOrKillOrderAsync(order, taker))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
describe('batch functions', () => {
let orders: Order[];
beforeEach(async () => {
orders = await Promise.all([
orderFactory.newSignedOrderAsync(),
orderFactory.newSignedOrderAsync(),
orderFactory.newSignedOrderAsync(),
]);
balances = await dmyBalances.getAsync();
});
describe('batchFillOrders', () => {
it('should transfer the correct amounts', async () => {
const fillTakerTokenAmounts: BigNumber[] = [];
const makerToken = rep.address;
const takerToken = dgd.address;
orders.forEach(order => {
const fillTakerTokenAmount = order.params.takerTokenAmount.div(2);
const fillMakerTokenAmount = fillTakerTokenAmount
.times(order.params.makerTokenAmount)
.dividedToIntegerBy(order.params.takerTokenAmount);
const makerFee = order.params.makerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
const takerFee = order.params.takerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
fillTakerTokenAmounts.push(fillTakerTokenAmount);
balances[maker][makerToken] = balances[maker][makerToken].minus(fillMakerTokenAmount);
balances[maker][takerToken] = balances[maker][takerToken].add(fillTakerTokenAmount);
balances[maker][zrx.address] = balances[maker][zrx.address].minus(makerFee);
balances[taker][makerToken] = balances[taker][makerToken].add(fillMakerTokenAmount);
balances[taker][takerToken] = balances[taker][takerToken].minus(fillTakerTokenAmount);
balances[taker][zrx.address] = balances[taker][zrx.address].minus(takerFee);
balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add(makerFee.add(takerFee));
});
await exWrapper.batchFillOrdersAsync(orders, taker, {fillTakerTokenAmounts});
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
});
describe('batchFillOrKillOrders', () => {
it('should transfer the correct amounts', async () => {
const fillTakerTokenAmounts: BigNumber[] = [];
const makerToken = rep.address;
const takerToken = dgd.address;
orders.forEach(order => {
const fillTakerTokenAmount = order.params.takerTokenAmount.div(2);
const fillMakerTokenAmount = fillTakerTokenAmount
.times(order.params.makerTokenAmount)
.dividedToIntegerBy(order.params.takerTokenAmount);
const makerFee = order.params.makerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
const takerFee = order.params.takerFee
.times(fillMakerTokenAmount)
.dividedToIntegerBy(order.params.makerTokenAmount);
fillTakerTokenAmounts.push(fillTakerTokenAmount);
balances[maker][makerToken] = balances[maker][makerToken].minus(fillMakerTokenAmount);
balances[maker][takerToken] = balances[maker][takerToken].add(fillTakerTokenAmount);
balances[maker][zrx.address] = balances[maker][zrx.address].minus(makerFee);
balances[taker][makerToken] = balances[taker][makerToken].add(fillMakerTokenAmount);
balances[taker][takerToken] = balances[taker][takerToken].minus(fillTakerTokenAmount);
balances[taker][zrx.address] = balances[taker][zrx.address].minus(takerFee);
balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add(makerFee.add(takerFee));
});
await exWrapper.batchFillOrKillOrdersAsync(orders, taker, {fillTakerTokenAmounts});
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should throw if a single order does not fill the expected amount', async () => {
const fillTakerTokenAmounts: BigNumber[] = [];
const makerToken = rep.address;
const takerToken = dgd.address;
orders.forEach(order => {
const fillTakerTokenAmount = order.params.takerTokenAmount.div(2);
fillTakerTokenAmounts.push(fillTakerTokenAmount);
});
await exWrapper.fillOrKillOrderAsync(orders[0], taker);
return expect(exWrapper.batchFillOrKillOrdersAsync(orders, taker, {fillTakerTokenAmounts}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
describe('fillOrdersUpTo', () => {
it('should stop when the entire fillTakerTokenAmount is filled', async () => {
const fillTakerTokenAmount = orders[0].params.takerTokenAmount.plus(orders[1].params.takerTokenAmount.div(2));
await exWrapper.fillOrdersUpToAsync(orders, taker, {fillTakerTokenAmount});
const newBalances = await dmyBalances.getAsync();
const fillMakerTokenAmount = orders[0].params.makerTokenAmount.add(
orders[1].params.makerTokenAmount.dividedToIntegerBy(2));
const makerFee = orders[0].params.makerFee.add(orders[1].params.makerFee.dividedToIntegerBy(2));
const takerFee = orders[0].params.takerFee.add(orders[1].params.takerFee.dividedToIntegerBy(2));
expect(newBalances[maker][orders[0].params.makerToken])
.to.be.bignumber.equal(balances[maker][orders[0].params.makerToken].minus(fillMakerTokenAmount));
expect(newBalances[maker][orders[0].params.takerToken])
.to.be.bignumber.equal(balances[maker][orders[0].params.takerToken].add(fillTakerTokenAmount));
expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(balances[maker][zrx.address].minus(makerFee));
expect(newBalances[taker][orders[0].params.takerToken])
.to.be.bignumber.equal(balances[taker][orders[0].params.takerToken].minus(fillTakerTokenAmount));
expect(newBalances[taker][orders[0].params.makerToken])
.to.be.bignumber.equal(balances[taker][orders[0].params.makerToken].add(fillMakerTokenAmount));
expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(takerFee));
expect(newBalances[feeRecipient][zrx.address])
.to.be.bignumber.equal(balances[feeRecipient][zrx.address].add(makerFee.add(takerFee)));
});
it('should fill all orders if cannot fill entire fillTakerTokenAmount', async () => {
const fillTakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18);
orders.forEach(order => {
balances[maker][order.params.makerToken] = balances[maker][order.params.makerToken]
.minus(order.params.makerTokenAmount);
balances[maker][order.params.takerToken] = balances[maker][order.params.takerToken]
.add(order.params.takerTokenAmount);
balances[maker][zrx.address] = balances[maker][zrx.address]
.minus(order.params.makerFee);
balances[taker][order.params.makerToken] = balances[taker][order.params.makerToken]
.add(order.params.makerTokenAmount);
balances[taker][order.params.takerToken] = balances[taker][order.params.takerToken]
.minus(order.params.takerTokenAmount);
balances[taker][zrx.address] = balances[taker][zrx.address].minus(order.params.takerFee);
balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address]
.add(order.params.makerFee
.add(order.params.takerFee));
});
await exWrapper.fillOrdersUpToAsync(orders, taker, {fillTakerTokenAmount});
const newBalances = await dmyBalances.getAsync();
expect(newBalances).to.be.deep.equal(balances);
});
it('should throw when an order does not use the same takerToken', async () => {
orders = await Promise.all([
orderFactory.newSignedOrderAsync(),
orderFactory.newSignedOrderAsync({takerToken: zrx.address}),
orderFactory.newSignedOrderAsync(),
]);
return expect(
exWrapper.fillOrdersUpToAsync(
orders, taker, {fillTakerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1000), 18)}),
).to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
describe('batchCancelOrders', () => {
it('should be able to cancel multiple orders', async () => {
const cancelTakerTokenAmounts = _.map(orders, order => order.params.takerTokenAmount);
await exWrapper.batchCancelOrdersAsync(orders, maker, {cancelTakerTokenAmounts});
const res = await exWrapper.batchFillOrdersAsync(
orders, taker, {fillTakerTokenAmounts: cancelTakerTokenAmounts});
const newBalances = await dmyBalances.getAsync();
expect(balances).to.be.deep.equal(newBalances);
});
});
});
});

View File

@@ -0,0 +1,111 @@
import {BigNumber} from 'bignumber.js';
import * as chai from 'chai';
import promisify = require('es6-promisify');
import Web3 = require('web3');
import * as multiSigWalletJSON from '../../build/contracts/MultiSigWalletWithTimeLock.json';
import {Artifacts} from '../../util/artifacts';
import {constants} from '../../util/constants';
import {MultiSigWrapper} from '../../util/multi_sig_wrapper';
import {RPC} from '../../util/rpc';
import {ContractInstance} from '../../util/types';
import {chaiSetup} from './utils/chai_setup';
const {MultiSigWalletWithTimeLock} = new Artifacts(artifacts);
const MULTI_SIG_ABI = (multiSigWalletJSON as any).abi;
chaiSetup.configure();
const expect = chai.expect;
// In order to benefit from type-safety, we re-assign the global web3 instance injected by Truffle
// with type `any` to a variable of type `Web3`.
const web3: Web3 = (global as any).web3;
contract('MultiSigWalletWithTimeLock', (accounts: string[]) => {
const owners = [accounts[0], accounts[1]];
const SECONDS_TIME_LOCKED = 10000;
let multiSig: ContractInstance;
let multiSigWrapper: MultiSigWrapper;
let txId: number;
let initialSecondsTimeLocked: number;
let rpc: RPC;
before(async () => {
multiSig = await MultiSigWalletWithTimeLock.deployed();
multiSigWrapper = new MultiSigWrapper(multiSig);
const secondsTimeLocked = await multiSig.secondsTimeLocked.call();
initialSecondsTimeLocked = secondsTimeLocked.toNumber();
rpc = new RPC();
});
describe('changeTimeLock', () => {
it('should throw when not called by wallet', async () => {
return expect(multiSig.changeTimeLock(SECONDS_TIME_LOCKED, {from: owners[0]}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw without enough confirmations', async () => {
const destination = multiSig.address;
const from = owners[0];
const dataParams = {
name: 'changeTimeLock',
abi: MULTI_SIG_ABI,
args: [SECONDS_TIME_LOCKED],
};
const subRes = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams);
txId = subRes.logs[0].args.transactionId.toNumber();
return expect(multiSig.executeTransaction(txId)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should set confirmation time with enough confirmations', async () => {
const res = await multiSig.confirmTransaction(txId, {from: owners[1]});
expect(res.logs).to.have.length(2);
const blockNum = await promisify(web3.eth.getBlockNumber)();
const blockInfo = await promisify(web3.eth.getBlock)(blockNum);
const timestamp = new BigNumber(blockInfo.timestamp);
const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes.call(txId));
expect(timestamp).to.be.bignumber.equal(confirmationTimeBigNum);
});
it('should be executable with enough confirmations and secondsTimeLocked of 0', async () => {
expect(initialSecondsTimeLocked).to.be.equal(0);
const res = await multiSig.executeTransaction(txId);
expect(res.logs).to.have.length(2);
const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call());
expect(secondsTimeLocked).to.be.bignumber.equal(SECONDS_TIME_LOCKED);
});
const newSecondsTimeLocked = 0;
it('should throw if it has enough confirmations but is not past the time lock', async () => {
const destination = multiSig.address;
const from = owners[0];
const dataParams = {
name: 'changeTimeLock',
abi: MULTI_SIG_ABI,
args: [newSecondsTimeLocked],
};
const subRes = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams);
txId = subRes.logs[0].args.transactionId.toNumber();
const confRes = await multiSig.confirmTransaction(txId, {from: owners[1]});
expect(confRes.logs).to.have.length(2);
return expect(multiSig.executeTransaction(txId)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should execute if it has enough confirmations and is past the time lock', async () => {
await rpc.increaseTimeAsync(SECONDS_TIME_LOCKED);
await multiSig.executeTransaction(txId);
const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call());
expect(secondsTimeLocked).to.be.bignumber.equal(newSecondsTimeLocked);
});
});
});

View File

@@ -0,0 +1,136 @@
import * as chai from 'chai';
import * as tokenTransferProxyJSON from '../../build/contracts/TokenTransferProxy.json';
import {Artifacts} from '../../util/artifacts';
import {constants} from '../../util/constants';
import {crypto} from '../../util/crypto';
import {MultiSigWrapper} from '../../util/multi_sig_wrapper';
import {ContractInstance, TransactionDataParams} from '../../util/types';
import {chaiSetup} from './utils/chai_setup';
const {TokenTransferProxy, MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress} = new Artifacts(artifacts);
const PROXY_ABI = (tokenTransferProxyJSON as any).abi;
chaiSetup.configure();
const expect = chai.expect;
contract('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', (accounts: string[]) => {
const owners = [accounts[0], accounts[1]];
const requiredApprovals = 2;
const SECONDS_TIME_LOCKED = 1000000;
// initialize fake addresses
const authorizedAddress = `0x${crypto.solSHA3([accounts[0]]).slice(0, 20).toString('hex')}`;
const unauthorizedAddress = `0x${crypto.solSHA3([accounts[1]]).slice(0, 20).toString('hex')}`;
let tokenTransferProxy: ContractInstance;
let multiSig: ContractInstance;
let multiSigWrapper: MultiSigWrapper;
let validDestination: string;
beforeEach(async () => {
const initialOwner = accounts[0];
tokenTransferProxy = await TokenTransferProxy.new({from: initialOwner});
await tokenTransferProxy.addAuthorizedAddress(authorizedAddress, {from: initialOwner});
multiSig = await MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.new(
owners, requiredApprovals, SECONDS_TIME_LOCKED, tokenTransferProxy.address);
await tokenTransferProxy.transferOwnership(multiSig.address, {from: initialOwner});
multiSigWrapper = new MultiSigWrapper(multiSig);
validDestination = tokenTransferProxy.address;
});
describe('isFunctionRemoveAuthorizedAddress', () => {
it('should throw if data is not for removeAuthorizedAddress', async () => {
const data = multiSigWrapper.encodeFnArgs('addAuthorizedAddress', PROXY_ABI, [owners[0]]);
return expect(multiSig.isFunctionRemoveAuthorizedAddress.call(data)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should return true if data is for removeAuthorizedAddress', async () => {
const data = multiSigWrapper.encodeFnArgs('removeAuthorizedAddress', PROXY_ABI, [owners[0]]);
const isFunctionRemoveAuthorizedAddress = await multiSig.isFunctionRemoveAuthorizedAddress.call(data);
expect(isFunctionRemoveAuthorizedAddress).to.be.true();
});
});
describe('executeRemoveAuthorizedAddress', () => {
it('should throw without the required confirmations', async () => {
const dataParams: TransactionDataParams = {
name: 'removeAuthorizedAddress',
abi: PROXY_ABI,
args: [authorizedAddress],
};
const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams);
const txId = res.logs[0].args.transactionId.toString();
return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if tx destination is not the tokenTransferProxy', async () => {
const invalidTokenTransferProxy = await TokenTransferProxy.new();
const invalidDestination = invalidTokenTransferProxy.address;
const dataParams: TransactionDataParams = {
name: 'removeAuthorizedAddress',
abi: PROXY_ABI,
args: [authorizedAddress],
};
const res = await multiSigWrapper.submitTransactionAsync(invalidDestination, owners[0], dataParams);
const txId = res.logs[0].args.transactionId.toString();
await multiSig.confirmTransaction(txId, {from: owners[1]});
const isConfirmed = await multiSig.isConfirmed.call(txId);
expect(isConfirmed).to.be.true();
return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if tx data is not for removeAuthorizedAddress', async () => {
const dataParams: TransactionDataParams = {
name: 'addAuthorizedAddress',
abi: PROXY_ABI,
args: [unauthorizedAddress],
};
const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams);
const txId = res.logs[0].args.transactionId.toString();
await multiSig.confirmTransaction(txId, {from: owners[1]});
const isConfirmed = await multiSig.isConfirmed.call(txId);
expect(isConfirmed).to.be.true();
return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should execute removeAuthorizedAddress for valid tokenTransferProxy if fully confirmed', async () => {
const dataParams: TransactionDataParams = {
name: 'removeAuthorizedAddress',
abi: PROXY_ABI,
args: [authorizedAddress],
};
const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams);
const txId = res.logs[0].args.transactionId.toString();
await multiSig.confirmTransaction(txId, {from: owners[1]});
const isConfirmed = await multiSig.isConfirmed.call(txId);
expect(isConfirmed).to.be.true();
await multiSig.executeRemoveAuthorizedAddress(txId);
const isAuthorized = await tokenTransferProxy.authorized.call(authorizedAddress);
expect(isAuthorized).to.be.false();
});
it('should throw if already executed', async () => {
const dataParams: TransactionDataParams = {
name: 'removeAuthorizedAddress',
abi: PROXY_ABI,
args: [authorizedAddress],
};
const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams);
const txId = res.logs[0].args.transactionId.toString();
await multiSig.confirmTransaction(txId, {from: owners[1]});
const isConfirmed = await multiSig.isConfirmed.call(txId);
expect(isConfirmed).to.be.true();
await multiSig.executeRemoveAuthorizedAddress(txId);
const tx = await multiSig.transactions.call(txId);
const isExecuted = tx[3];
expect(isExecuted).to.be.true();
return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
});

View File

@@ -0,0 +1,212 @@
import {ZeroEx} from '0x.js';
import * as chai from 'chai';
import ethUtil = require('ethereumjs-util');
import * as _ from 'lodash';
import {Artifacts} from '../../util/artifacts';
import {constants} from '../../util/constants';
import {TokenRegWrapper} from '../../util/token_registry_wrapper';
import {ContractInstance} from '../../util/types';
import {chaiSetup} from './utils/chai_setup';
const {TokenRegistry} = new Artifacts(artifacts);
chaiSetup.configure();
const expect = chai.expect;
contract('TokenRegistry', (accounts: string[]) => {
const owner = accounts[0];
const notOwner = accounts[1];
const tokenAddress1 = `0x${ethUtil.setLength(ethUtil.toBuffer('0x1'), 20, false).toString('hex')}`;
const tokenAddress2 = `0x${ethUtil.setLength(ethUtil.toBuffer('0x2'), 20, false).toString('hex')}`;
const token1 = {
address: tokenAddress1,
name: 'testToken1',
symbol: 'TT1',
decimals: 18,
ipfsHash: `0x${ethUtil.sha3('ipfs1').toString('hex')}`,
swarmHash: `0x${ethUtil.sha3('swarm1').toString('hex')}`,
};
const token2 = {
address: tokenAddress2,
name: 'testToken2',
symbol: 'TT2',
decimals: 18,
ipfsHash: `0x${ethUtil.sha3('ipfs2').toString('hex')}`,
swarmHash: `0x${ethUtil.sha3('swarm2').toString('hex')}`,
};
const nullToken = {
address: ZeroEx.NULL_ADDRESS,
name: '',
symbol: '',
decimals: 0,
ipfsHash: constants.NULL_BYTES,
swarmHash: constants.NULL_BYTES,
};
let tokenReg: ContractInstance;
let tokenRegWrapper: TokenRegWrapper;
beforeEach(async () => {
tokenReg = await TokenRegistry.new();
tokenRegWrapper = new TokenRegWrapper(tokenReg);
});
describe('addToken', () => {
it('should throw when not called by owner', async () => {
return expect(tokenRegWrapper.addTokenAsync(token1, notOwner)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should add token metadata when called by owner', async () => {
await tokenRegWrapper.addTokenAsync(token1, owner);
const tokenData = await tokenRegWrapper.getTokenMetaDataAsync(token1.address);
expect(tokenData).to.be.deep.equal(token1);
});
it('should throw if token already exists', async () => {
await tokenRegWrapper.addTokenAsync(token1, owner);
return expect(tokenRegWrapper.addTokenAsync(token1, owner)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if token address is null', async () => {
return expect(tokenRegWrapper.addTokenAsync(nullToken, owner)).to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if name already exists', async () => {
await tokenRegWrapper.addTokenAsync(token1, owner);
const duplicateNameToken = _.assign({}, token2, {name: token1.name});
return expect(tokenRegWrapper.addTokenAsync(duplicateNameToken, owner))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if symbol already exists', async () => {
await tokenRegWrapper.addTokenAsync(token1, owner);
const duplicateSymbolToken = _.assign({}, token2, {symbol: token1.symbol});
return expect(tokenRegWrapper.addTokenAsync(duplicateSymbolToken, owner))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
describe('after addToken', () => {
beforeEach(async () => {
await tokenRegWrapper.addTokenAsync(token1, owner);
});
describe('getTokenByName', () => {
it('should return token metadata when given the token name', async () => {
const tokenData = await tokenRegWrapper.getTokenByNameAsync(token1.name);
expect(tokenData).to.be.deep.equal(token1);
});
});
describe('getTokenBySymbol', () => {
it('should return token metadata when given the token symbol', async () => {
const tokenData = await tokenRegWrapper.getTokenBySymbolAsync(token1.symbol);
expect(tokenData).to.be.deep.equal(token1);
});
});
describe('setTokenName', () => {
it('should throw when not called by owner', async () => {
return expect(tokenReg.setTokenName(token1.address, token2.name, {from: notOwner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should change the token name when called by owner', async () => {
const res = await tokenReg.setTokenName(token1.address, token2.name, {from: owner});
expect(res.logs).to.have.length(1);
const [newData, oldData] = await Promise.all([
tokenRegWrapper.getTokenByNameAsync(token2.name),
tokenRegWrapper.getTokenByNameAsync(token1.name),
]);
const expectedNewData = _.assign({}, token1, {name: token2.name});
const expectedOldData = nullToken;
expect(newData).to.be.deep.equal(expectedNewData);
expect(oldData).to.be.deep.equal(expectedOldData);
});
it('should throw if the name already exists', async () => {
await tokenRegWrapper.addTokenAsync(token2, owner);
return expect(tokenReg.setTokenName(token1.address, token2.name, {from: owner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if token does not exist', async () => {
return expect(tokenReg.setTokenName(nullToken.address, token2.name, {from: owner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
describe('setTokenSymbol', () => {
it('should throw when not called by owner', async () => {
return expect(tokenReg.setTokenSymbol(token1.address, token2.symbol, {from: notOwner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should change the token symbol when called by owner', async () => {
const res = await tokenReg.setTokenSymbol(token1.address, token2.symbol, {from: owner});
expect(res.logs).to.have.length(1);
const [newData, oldData] = await Promise.all([
tokenRegWrapper.getTokenBySymbolAsync(token2.symbol),
tokenRegWrapper.getTokenBySymbolAsync(token1.symbol),
]);
const expectedNewData = _.assign({}, token1, {symbol: token2.symbol});
const expectedOldData = nullToken;
expect(newData).to.be.deep.equal(expectedNewData);
expect(oldData).to.be.deep.equal(expectedOldData);
});
it('should throw if the symbol already exists', async () => {
await tokenRegWrapper.addTokenAsync(token2, owner);
return expect(tokenReg.setTokenSymbol(token1.address, token2.symbol, {from: owner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if token does not exist', async () => {
return expect(tokenReg.setTokenSymbol(nullToken.address, token2.symbol, {from: owner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
describe('removeToken', () => {
it('should throw if not called by owner', async () => {
const index = 0;
return expect(tokenReg.removeToken(token1.address, index, {from: notOwner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should remove token metadata when called by owner', async () => {
const index = 0;
const res = await tokenReg.removeToken(token1.address, index, {from: owner});
expect(res.logs).to.have.length(1);
const tokenData = await tokenRegWrapper.getTokenMetaDataAsync(token1.address);
expect(tokenData).to.be.deep.equal(nullToken);
});
it('should throw if token does not exist', async () => {
const index = 0;
return expect(tokenReg.removeToken(nullToken.address, index, {from: owner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should throw if token at given index does not match address', async () => {
await tokenRegWrapper.addTokenAsync(token2, owner);
const incorrectIndex = 0;
return expect(tokenReg.removeToken(token2.address, incorrectIndex, {from: owner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
});
});

View File

@@ -0,0 +1,83 @@
import * as chai from 'chai';
import {constants} from '../../../util/constants';
import {ContractInstance} from '../../../util/types';
import {chaiSetup} from '../utils/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
const TokenTransferProxy = artifacts.require('./db/TokenTransferProxy.sol');
contract('TokenTransferProxy', (accounts: string[]) => {
const owner = accounts[0];
const notOwner = accounts[1];
let tokenTransferProxy: ContractInstance;
let authorized: string;
let notAuthorized = owner;
before(async () => {
tokenTransferProxy = await TokenTransferProxy.deployed();
});
describe('addAuthorizedAddress', () => {
it('should throw if not called by owner', async () => {
return expect(tokenTransferProxy.addAuthorizedAddress(notOwner, {from: notOwner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should allow owner to add an authorized address', async () => {
await tokenTransferProxy.addAuthorizedAddress(notAuthorized, {from: owner});
authorized = notAuthorized;
notAuthorized = null;
const isAuthorized = await tokenTransferProxy.authorized.call(authorized);
expect(isAuthorized).to.be.true();
});
it('should throw if owner attempts to authorize a duplicate address', async () => {
return expect(tokenTransferProxy.addAuthorizedAddress(authorized, {from: owner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
describe('removeAuthorizedAddress', () => {
it('should throw if not called by owner', async () => {
return expect(tokenTransferProxy.removeAuthorizedAddress(authorized, {from: notOwner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should allow owner to remove an authorized address', async () => {
await tokenTransferProxy.removeAuthorizedAddress(authorized, {from: owner});
notAuthorized = authorized;
authorized = null;
const isAuthorized = await tokenTransferProxy.authorized.call(notAuthorized);
expect(isAuthorized).to.be.false();
});
it('should throw if owner attempts to remove an address that is not authorized', async () => {
return expect(tokenTransferProxy.removeAuthorizedAddress(notAuthorized, {from: owner}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
});
describe('getAuthorizedAddresses', () => {
it('should return all authorized addresses', async () => {
const initial = await tokenTransferProxy.getAuthorizedAddresses();
expect(initial).to.have.length(1);
await tokenTransferProxy.addAuthorizedAddress(notAuthorized, {from: owner});
authorized = notAuthorized;
notAuthorized = null;
const afterAdd = await tokenTransferProxy.getAuthorizedAddresses();
expect(afterAdd).to.have.length(2);
expect(afterAdd).to.include(authorized);
await tokenTransferProxy.removeAuthorizedAddress(authorized, {from: owner});
notAuthorized = authorized;
authorized = null;
const afterRemove = await tokenTransferProxy.getAuthorizedAddresses();
expect(afterRemove).to.have.length(1);
});
});
});

View File

@@ -0,0 +1,66 @@
import * as chai from 'chai';
import {Artifacts} from '../../../util/artifacts';
import {Balances} from '../../../util/balances';
import {constants} from '../../../util/constants';
import {ContractInstance} from '../../../util/types';
import {chaiSetup} from '../utils/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
const {
TokenTransferProxy,
DummyToken,
TokenRegistry,
} = new Artifacts(artifacts);
contract('TokenTransferProxy', (accounts: string[]) => {
const INIT_BAL = 100000000;
const INIT_ALLOW = 100000000;
const owner = accounts[0];
const notAuthorized = owner;
let tokenTransferProxy: ContractInstance;
let tokenRegistry: ContractInstance;
let rep: ContractInstance;
let dmyBalances: Balances;
before(async () => {
[tokenTransferProxy, tokenRegistry] = await Promise.all([
TokenTransferProxy.deployed(),
TokenRegistry.deployed(),
]);
const repAddress = await tokenRegistry.getTokenAddressBySymbol('REP');
rep = DummyToken.at(repAddress);
dmyBalances = new Balances([rep], [accounts[0], accounts[1]]);
await Promise.all([
rep.approve(TokenTransferProxy.address, INIT_ALLOW, {from: accounts[0]}),
rep.setBalance(accounts[0], INIT_BAL, {from: owner}),
rep.approve(TokenTransferProxy.address, INIT_ALLOW, {from: accounts[1]}),
rep.setBalance(accounts[1], INIT_BAL, {from: owner}),
]);
});
describe('transferFrom', () => {
it('should throw when called by an unauthorized address', async () => {
expect(tokenTransferProxy.transferFrom(rep.address, accounts[0], accounts[1], 1000, {from: notAuthorized}))
.to.be.rejectedWith(constants.INVALID_OPCODE);
});
it('should allow an authorized address to transfer', async () => {
const balances = await dmyBalances.getAsync();
await tokenTransferProxy.addAuthorizedAddress(notAuthorized, {from: owner});
const transferAmt = 10000;
await tokenTransferProxy.transferFrom(rep.address, accounts[0], accounts[1], transferAmt, {from: notAuthorized});
const newBalances = await dmyBalances.getAsync();
expect(newBalances[accounts[0]][rep.address])
.to.be.bignumber.equal(balances[accounts[0]][rep.address].minus(transferAmt));
expect(newBalances[accounts[1]][rep.address])
.to.be.bignumber.equal(balances[accounts[1]][rep.address].add(transferAmt));
});
});
});

View File

@@ -0,0 +1,115 @@
import {ZeroEx} from '0x.js';
import {BigNumber} from 'bignumber.js';
import * as chai from 'chai';
import * as Web3 from 'web3';
import {Artifacts} from '../../util/artifacts';
import {ContractInstance} from '../../util/types';
import {chaiSetup} from './utils/chai_setup';
const {DummyToken} = new Artifacts(artifacts);
const web3: Web3 = (global as any).web3;
chaiSetup.configure();
const expect = chai.expect;
contract('UnlimitedAllowanceToken', (accounts: string[]) => {
const zeroEx = new ZeroEx(web3.currentProvider);
const owner = accounts[0];
const spender = accounts[1];
const MAX_MINT_VALUE = new BigNumber(100000000000000000000);
let tokenAddress: string;
let token: ContractInstance;
beforeEach(async () => {
token = await DummyToken.new({from: owner});
await token.mint(MAX_MINT_VALUE, {from: owner});
tokenAddress = token.address;
});
describe('transfer', () => {
it('should transfer balance from sender to receiver', async () => {
const receiver = spender;
const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner);
const amountToTransfer = new BigNumber(1);
await zeroEx.token.transferAsync(tokenAddress, owner, receiver, amountToTransfer);
const finalOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner);
const finalReceiverBalance = await zeroEx.token.getBalanceAsync(tokenAddress, receiver);
const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer);
const expectedFinalReceiverBalance = amountToTransfer;
expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance);
expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance);
});
it('should return true on a 0 value transfer', async () => {
const didReturnTrue = await token.transfer.call(spender, 0, {from: owner});
expect(didReturnTrue).to.be.true();
});
});
describe('transferFrom', () => {
it('should return false if owner has insufficient balance', async () => {
const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner);
const amountToTransfer = ownerBalance.plus(1);
await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, amountToTransfer);
const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, {from: spender});
expect(didReturnTrue).to.be.false();
});
it('should return false if spender has insufficient allowance', async () => {
const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner);
const amountToTransfer = ownerBalance;
const spenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender);
const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0;
expect(spenderAllowanceIsInsufficient).to.be.true();
const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, {from: spender});
expect(didReturnTrue).to.be.false();
});
it('should return true on a 0 value transfer', async () => {
const amountToTransfer = 0;
const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, {from: spender});
expect(didReturnTrue).to.be.true();
});
it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => {
const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner);
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance);
await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer);
const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender);
expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance);
});
it('should transfer the correct balances if spender has sufficient allowance', async () => {
const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner);
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = initOwnerBalance;
await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance);
await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer);
const newOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner);
const newSpenderBalance = await zeroEx.token.getBalanceAsync(tokenAddress, spender);
expect(newOwnerBalance).to.be.bignumber.equal(0);
expect(newSpenderBalance).to.be.bignumber.equal(initOwnerBalance);
});
it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => {
const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner);
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = initOwnerBalance;
await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance);
await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer);
const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender);
expect(newSpenderAllowance).to.be.bignumber.equal(0);
});
});
});

View File

@@ -0,0 +1,13 @@
import * as chai from 'chai';
import chaiAsPromised = require('chai-as-promised');
import ChaiBigNumber = require('chai-bignumber');
import * as dirtyChai from 'dirty-chai';
export const chaiSetup = {
configure() {
chai.config.includeStack = true;
chai.use(ChaiBigNumber());
chai.use(dirtyChai);
chai.use(chaiAsPromised);
},
};

View File

@@ -0,0 +1,168 @@
import {ZeroEx} from '0x.js';
import {BigNumber} from 'bignumber.js';
import * as chai from 'chai';
import Web3 = require('web3');
import {Artifacts} from '../../util/artifacts';
import {ContractInstance} from '../../util/types';
import {chaiSetup} from './utils/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
const {Exchange, ZRXToken} = new Artifacts(artifacts);
const web3: Web3 = (global as any).web3;
contract('ZRXToken', (accounts: string[]) => {
const owner = accounts[0];
const spender = accounts[1];
let zeroEx: ZeroEx;
let MAX_UINT: BigNumber;
let zrx: ContractInstance;
let zrxAddress: string;
beforeEach(async () => {
zeroEx = new ZeroEx(web3.currentProvider, {
exchangeContractAddress: Exchange.address,
});
zrxAddress = await zeroEx.exchange.getZRXTokenAddressAsync();
zrx = await ZRXToken.at(zrxAddress);
MAX_UINT = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
});
describe('constants', () => {
it('should have 18 decimals', async () => {
const decimals = new BigNumber(await zrx.decimals.call());
const expectedDecimals = 18;
expect(decimals).to.be.bignumber.equal(expectedDecimals);
});
it('should have a total supply of 1 billion tokens', async () => {
const totalSupply = new BigNumber(await zrx.totalSupply.call());
const expectedTotalSupply = 1000000000;
expect(ZeroEx.toUnitAmount(totalSupply, 18)).to.be.bignumber.equal(expectedTotalSupply);
});
it('should be named 0x Protocol Token', async () => {
const name = await zrx.name.call();
const expectedName = '0x Protocol Token';
expect(name).to.be.equal(expectedName);
});
it('should have the symbol ZRX', async () => {
const symbol = await zrx.symbol.call();
const expectedSymbol = 'ZRX';
expect(symbol).to.be.equal(expectedSymbol);
});
});
describe('constructor', () => {
it('should initialize owner balance to totalSupply', async () => {
const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
const totalSupply = new BigNumber(await zrx.totalSupply.call());
expect(totalSupply).to.be.bignumber.equal(ownerBalance);
});
});
describe('transfer', () => {
it('should transfer balance from sender to receiver', async () => {
const receiver = spender;
const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
const amountToTransfer = new BigNumber(1);
const txHash = await zeroEx.token.transferAsync(zrxAddress, owner, receiver, amountToTransfer);
await zeroEx.awaitTransactionMinedAsync(txHash);
const finalOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
const finalReceiverBalance = await zeroEx.token.getBalanceAsync(zrxAddress, receiver);
const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer);
const expectedFinalReceiverBalance = amountToTransfer;
expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance);
expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance);
});
it('should return true on a 0 value transfer', async () => {
const didReturnTrue = await zrx.transfer.call(spender, 0, {from: owner});
expect(didReturnTrue).to.be.true();
});
});
describe('transferFrom', () => {
it('should return false if owner has insufficient balance', async () => {
const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
const amountToTransfer = ownerBalance.plus(1);
let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer);
await zeroEx.awaitTransactionMinedAsync(txHash);
const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, {from: spender});
expect(didReturnTrue).to.be.false();
// Reset allowance
txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, new BigNumber(0));
await zeroEx.awaitTransactionMinedAsync(txHash);
});
it('should return false if spender has insufficient allowance', async () => {
const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
const amountToTransfer = ownerBalance;
const spenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender);
const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0;
expect(spenderAllowanceIsInsufficient).to.be.true();
const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, {from: spender});
expect(didReturnTrue).to.be.false();
});
it('should return true on a 0 value transfer', async () => {
const amountToTransfer = 0;
const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, {from: spender});
expect(didReturnTrue).to.be.true();
});
it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => {
const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = MAX_UINT;
let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer);
await zeroEx.awaitTransactionMinedAsync(txHash);
const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender);
expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance);
// Restore balance
txHash = await zeroEx.token.transferAsync(zrxAddress, spender, owner, amountToTransfer);
await zeroEx.awaitTransactionMinedAsync(txHash);
});
it('should transfer the correct balances if spender has sufficient allowance', async () => {
const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
const initSpenderBalance = await zeroEx.token.getBalanceAsync(zrxAddress, spender);
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = initOwnerBalance;
let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer);
await zeroEx.awaitTransactionMinedAsync(txHash);
const newOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
const newSpenderBalance = await zeroEx.token.getBalanceAsync(zrxAddress, spender);
expect(newOwnerBalance).to.be.bignumber.equal(0);
expect(newSpenderBalance).to.be.bignumber.equal(initSpenderBalance.plus(initOwnerBalance));
});
it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => {
const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = initOwnerBalance;
let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer);
await zeroEx.awaitTransactionMinedAsync(txHash);
const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender);
expect(newSpenderAllowance).to.be.bignumber.equal(0);
});
});
});

View File

@@ -0,0 +1,17 @@
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*", // Match any network id
},
kovan: {
host: "localhost",
port: 8546,
network_id: "42",
gas: 4612388,
},
},
test_directory: "transpiled/test",
migrations_directory: "transpiled/migrations",
};

View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"outDir": "./transpiled/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es6",
"baseUrl": ".",
"allowJs": true
},
"include": [
"../../node_modules/types-ethereumjs-util/index.d.ts",
"../../node_modules/web3-typescript-typings/index.d.ts",
"../../node_modules/chai-typescript-typings/index.d.ts",
"../../node_modules/chai-as-promised-typescript-typings/index.d.ts",
"../../node_modules/types-ethereumjs-util/index.d.ts",
"../../node_modules/types-bn/index.d.ts",
"./globals.d.ts",
"./util/**/*",
"./test/**/*",
"./migrations/**/*",
"./deploy/**/*"
],
"exclude": [
"./deploy/solc/solc_bin"
]
}

View File

@@ -0,0 +1,5 @@
{
"extends": [
"@0xproject/tslint-config"
]
}

View File

@@ -0,0 +1,25 @@
export class Artifacts {
public Migrations: any;
public TokenTransferProxy: any;
public TokenRegistry: any;
public MultiSigWalletWithTimeLock: any;
public Exchange: any;
public ZRXToken: any;
public DummyToken: any;
public EtherToken: any;
public MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: any;
public MaliciousToken: any;
constructor(artifacts: any) {
this.Migrations = artifacts.require('Migrations');
this.TokenTransferProxy = artifacts.require('TokenTransferProxy');
this.TokenRegistry = artifacts.require('TokenRegistry');
this.MultiSigWalletWithTimeLock = artifacts.require('MultiSigWalletWithTimeLock');
this.Exchange = artifacts.require('Exchange');
this.ZRXToken = artifacts.require('ZRXToken');
this.DummyToken = artifacts.require('DummyToken');
this.EtherToken = artifacts.require('EtherToken');
this.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress = artifacts.require(
'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress');
this.MaliciousToken = artifacts.require('MaliciousToken');
}
}

View File

@@ -0,0 +1,30 @@
import {BigNumber} from 'bignumber.js';
import * as _ from 'lodash';
import {bigNumberConfigs} from './bignumber_config';
import {BalancesByOwner, ContractInstance} from './types';
bigNumberConfigs.configure();
export class Balances {
private tokenContractInstances: ContractInstance[];
private ownerAddresses: string[];
constructor(tokenContractInstances: ContractInstance[], ownerAddresses: string[]) {
this.tokenContractInstances = tokenContractInstances;
this.ownerAddresses = ownerAddresses;
}
public async getAsync(): Promise<BalancesByOwner> {
const balancesByOwner: BalancesByOwner = {};
for (const tokenContractInstance of this.tokenContractInstances) {
for (const ownerAddress of this.ownerAddresses) {
let balance = await tokenContractInstance.balanceOf(ownerAddress);
balance = new BigNumber(balance);
if (_.isUndefined(balancesByOwner[ownerAddress])) {
balancesByOwner[ownerAddress] = {};
}
balancesByOwner[ownerAddress][tokenContractInstance.address] = balance;
}
}
return balancesByOwner;
}
}

View File

@@ -0,0 +1,11 @@
import {BigNumber} from 'bignumber.js';
export const bigNumberConfigs = {
configure() {
// By default BigNumber's `toString` method converts to exponential notation if the value has
// more then 20 digits. We want to avoid this behavior, so we set EXPONENTIAL_AT to a high number
BigNumber.config({
EXPONENTIAL_AT: 1000,
});
},
};

View File

@@ -0,0 +1,4 @@
export const constants = {
NULL_BYTES: '0x',
INVALID_OPCODE: 'invalid opcode',
};

View File

@@ -0,0 +1,38 @@
import {BigNumber} from 'bignumber.js';
import BN = require('bn.js');
import ABI = require('ethereumjs-abi');
import ethUtil = require('ethereumjs-util');
import * as _ from 'lodash';
export const crypto = {
/*
* We convert types from JS to Solidity as follows:
* BigNumber -> uint256
* number -> uint8
* string -> string
* boolean -> bool
* valid Ethereum address -> address
*/
solSHA3(args: any[]): Buffer {
const argTypes: string[] = [];
_.each(args, (arg, i) => {
const isNumber = _.isFinite(arg);
if (isNumber) {
argTypes.push('uint8');
} else if ((arg).isBigNumber) {
argTypes.push('uint256');
args[i] = new BN(arg.toString(10), 10);
} else if (ethUtil.isValidAddress(arg)) {
argTypes.push('address');
} else if (_.isString(arg)) {
argTypes.push('string');
} else if (_.isBoolean(arg)) {
argTypes.push('bool');
} else {
throw new Error(`Unable to guess arg type: ${arg}`);
}
});
const hash = ABI.soliditySHA3(argTypes, args);
return hash;
},
};

View File

@@ -0,0 +1,168 @@
import {BigNumber} from 'bignumber.js';
import * as _ from 'lodash';
import {formatters} from './formatters';
import {Order} from './order';
import {ContractInstance} from './types';
export class ExchangeWrapper {
private exchange: ContractInstance;
constructor(exchangeContractInstance: ContractInstance) {
this.exchange = exchangeContractInstance;
}
public async fillOrderAsync(order: Order, from: string,
opts: {
fillTakerTokenAmount?: BigNumber;
shouldThrowOnInsufficientBalanceOrAllowance?: boolean;
} = {}) {
const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance;
const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmount);
const tx = await this.exchange.fillOrder(
params.orderAddresses,
params.orderValues,
params.fillTakerTokenAmount,
params.shouldThrowOnInsufficientBalanceOrAllowance,
params.v,
params.r,
params.s,
{from},
);
_.each(tx.logs, log => wrapLogBigNumbers(log));
return tx;
}
public async cancelOrderAsync(order: Order, from: string,
opts: {cancelTakerTokenAmount?: BigNumber} = {}) {
const params = order.createCancel(opts.cancelTakerTokenAmount);
const tx = await this.exchange.cancelOrder(
params.orderAddresses,
params.orderValues,
params.cancelTakerTokenAmount,
{from},
);
_.each(tx.logs, log => wrapLogBigNumbers(log));
return tx;
}
public async fillOrKillOrderAsync(order: Order, from: string,
opts: {fillTakerTokenAmount?: BigNumber} = {}) {
const shouldThrowOnInsufficientBalanceOrAllowance = true;
const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmount);
const tx = await this.exchange.fillOrKillOrder(
params.orderAddresses,
params.orderValues,
params.fillTakerTokenAmount,
params.v,
params.r,
params.s,
{from},
);
_.each(tx.logs, log => wrapLogBigNumbers(log));
return tx;
}
public async batchFillOrdersAsync(orders: Order[], from: string,
opts: {
fillTakerTokenAmounts?: BigNumber[];
shouldThrowOnInsufficientBalanceOrAllowance?: boolean;
} = {}) {
const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance;
const params = formatters.createBatchFill(
orders, shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmounts);
const tx = await this.exchange.batchFillOrders(
params.orderAddresses,
params.orderValues,
params.fillTakerTokenAmounts,
params.shouldThrowOnInsufficientBalanceOrAllowance,
params.v,
params.r,
params.s,
{from},
);
_.each(tx.logs, log => wrapLogBigNumbers(log));
return tx;
}
public async batchFillOrKillOrdersAsync(orders: Order[], from: string,
opts: {
fillTakerTokenAmounts?: BigNumber[];
} = {}) {
const params = formatters.createBatchFill(orders, undefined, opts.fillTakerTokenAmounts);
const tx = await this.exchange.batchFillOrKillOrders(
params.orderAddresses,
params.orderValues,
params.fillTakerTokenAmounts,
params.v,
params.r,
params.s,
{from},
);
_.each(tx.logs, log => wrapLogBigNumbers(log));
return tx;
}
public async fillOrdersUpToAsync(orders: Order[], from: string,
opts: {fillTakerTokenAmount?: BigNumber;
shouldThrowOnInsufficientBalanceOrAllowance?: boolean;
} = {}) {
const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance;
const params = formatters.createFillUpTo(orders,
shouldThrowOnInsufficientBalanceOrAllowance,
opts.fillTakerTokenAmount);
const tx = await this.exchange.fillOrdersUpTo(
params.orderAddresses,
params.orderValues,
params.fillTakerTokenAmount,
params.shouldThrowOnInsufficientBalanceOrAllowance,
params.v,
params.r,
params.s,
{from},
);
_.each(tx.logs, log => wrapLogBigNumbers(log));
return tx;
}
public async batchCancelOrdersAsync(orders: Order[], from: string,
opts: {cancelTakerTokenAmounts?: BigNumber[]} = {}) {
const params = formatters.createBatchCancel(orders, opts.cancelTakerTokenAmounts);
const tx = await this.exchange.batchCancelOrders(
params.orderAddresses,
params.orderValues,
params.cancelTakerTokenAmounts,
{from},
);
_.each(tx.logs, log => wrapLogBigNumbers(log));
return tx;
}
public async getOrderHashAsync(order: Order): Promise<string> {
const shouldThrowOnInsufficientBalanceOrAllowance = false;
const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance);
const orderHash = await this.exchange.getOrderHash(params.orderAddresses, params.orderValues);
return orderHash;
}
public async isValidSignatureAsync(order: Order): Promise<boolean> {
const isValidSignature = await this.exchange.isValidSignature(
order.params.maker,
order.params.orderHashHex,
order.params.v,
order.params.r,
order.params.s,
);
return isValidSignature;
}
public async isRoundingErrorAsync(numerator: BigNumber, denominator: BigNumber,
target: BigNumber): Promise<boolean> {
const isRoundingError = await this.exchange.isRoundingError(numerator, denominator, target);
return isRoundingError;
}
public async getPartialAmountAsync(numerator: BigNumber, denominator: BigNumber,
target: BigNumber): Promise<BigNumber> {
const partialAmount = new BigNumber(await this.exchange.getPartialAmount(numerator, denominator, target));
return partialAmount;
}
}
function wrapLogBigNumbers(log: any): any {
const argNames = _.keys(log.args);
for (const argName of argNames) {
const isWeb3BigNumber = _.startsWith(log.args[argName].constructor.toString(), 'function BigNumber(');
if (isWeb3BigNumber) {
log.args[argName] = new BigNumber(log.args[argName]);
}
}
}

View File

@@ -0,0 +1,74 @@
import {BigNumber} from 'bignumber.js';
import * as _ from 'lodash';
import {Order} from './order';
import {BatchCancelOrders, BatchFillOrders, FillOrdersUpTo} from './types';
export const formatters = {
createBatchFill(orders: Order[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
fillTakerTokenAmounts: BigNumber[] = []) {
const batchFill: BatchFillOrders = {
orderAddresses: [],
orderValues: [],
fillTakerTokenAmounts,
shouldThrowOnInsufficientBalanceOrAllowance,
v: [],
r: [],
s: [],
};
_.forEach(orders, order => {
batchFill.orderAddresses.push([order.params.maker, order.params.taker, order.params.makerToken,
order.params.takerToken, order.params.feeRecipient]);
batchFill.orderValues.push([order.params.makerTokenAmount, order.params.takerTokenAmount, order.params.makerFee,
order.params.takerFee, order.params.expirationTimestampInSec, order.params.salt]);
batchFill.v.push(order.params.v);
batchFill.r.push(order.params.r);
batchFill.s.push(order.params.s);
if (fillTakerTokenAmounts.length < orders.length) {
batchFill.fillTakerTokenAmounts.push(order.params.takerTokenAmount);
}
});
return batchFill;
},
createFillUpTo(orders: Order[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
fillTakerTokenAmount: BigNumber) {
const fillUpTo: FillOrdersUpTo = {
orderAddresses: [],
orderValues: [],
fillTakerTokenAmount,
shouldThrowOnInsufficientBalanceOrAllowance,
v: [],
r: [],
s: [],
};
orders.forEach(order => {
fillUpTo.orderAddresses.push([order.params.maker, order.params.taker, order.params.makerToken,
order.params.takerToken, order.params.feeRecipient]);
fillUpTo.orderValues.push([order.params.makerTokenAmount, order.params.takerTokenAmount, order.params.makerFee,
order.params.takerFee, order.params.expirationTimestampInSec, order.params.salt]);
fillUpTo.v.push(order.params.v);
fillUpTo.r.push(order.params.r);
fillUpTo.s.push(order.params.s);
});
return fillUpTo;
},
createBatchCancel(orders: Order[], cancelTakerTokenAmounts: BigNumber[] = []) {
const batchCancel: BatchCancelOrders = {
orderAddresses: [],
orderValues: [],
cancelTakerTokenAmounts,
};
orders.forEach(order => {
batchCancel.orderAddresses.push([order.params.maker, order.params.taker, order.params.makerToken,
order.params.takerToken, order.params.feeRecipient]);
batchCancel.orderValues.push([order.params.makerTokenAmount, order.params.takerTokenAmount, order.params.makerFee,
order.params.takerFee, order.params.expirationTimestampInSec, order.params.salt]);
if (cancelTakerTokenAmounts.length < orders.length) {
batchCancel.cancelTakerTokenAmounts.push(order.params.takerTokenAmount);
}
});
return batchCancel;
},
};

View File

@@ -0,0 +1,34 @@
import ABI = require('ethereumjs-abi');
import ethUtil = require('ethereumjs-util');
import * as _ from 'lodash';
import * as Web3 from 'web3';
import {ContractInstance, TransactionDataParams} from './types';
export class MultiSigWrapper {
private multiSig: ContractInstance;
constructor(multiSigContractInstance: ContractInstance) {
this.multiSig = multiSigContractInstance;
}
public async submitTransactionAsync(destination: string, from: string,
dataParams: TransactionDataParams,
value: number = 0) {
const {name, abi, args = []} = dataParams;
const encoded = this.encodeFnArgs(name, abi, args);
return this.multiSig.submitTransaction(destination, value, encoded, {from});
}
public encodeFnArgs(name: string, abi: Web3.AbiDefinition[], args: any[]) {
const abiEntity = _.find(abi, {name}) as Web3.MethodAbi;
if (_.isUndefined(abiEntity)) {
throw new Error(`Did not find abi entry for name: ${name}`);
}
const types = _.map(abiEntity.inputs, input => input.type);
const funcSig = ethUtil.bufferToHex(ABI.methodID(name, types));
const argsData = _.map(args, arg => {
const target = _.isBoolean(arg) ? +arg : arg;
const targetBuff = ethUtil.toBuffer(target);
return ethUtil.setLengthLeft(targetBuff, 32).toString('hex');
});
return funcSig + argsData.join('');
}
}

View File

@@ -0,0 +1,109 @@
import {BigNumber} from 'bignumber.js';
import promisify = require('es6-promisify');
import ethUtil = require('ethereumjs-util');
import * as _ from 'lodash';
import Web3 = require('web3');
import {crypto} from './crypto';
import {OrderParams} from './types';
// In order to benefit from type-safety, we re-assign the global web3 instance injected by Truffle
// with type `any` to a variable of type `Web3`.
const web3: Web3 = (global as any).web3;
export class Order {
public params: OrderParams;
constructor(params: OrderParams) {
this.params = params;
}
public isValidSignature() {
const {v, r, s} = this.params;
if (_.isUndefined(v) || _.isUndefined(r) || _.isUndefined(s)) {
throw new Error('Cannot call isValidSignature on unsigned order');
}
const orderHash = this.getOrderHash();
const msgHash = ethUtil.hashPersonalMessage(ethUtil.toBuffer(orderHash));
try {
const pubKey = ethUtil.ecrecover(msgHash, v, ethUtil.toBuffer(r), ethUtil.toBuffer(s));
const recoveredAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
return recoveredAddress === this.params.maker;
} catch (err) {
return false;
}
}
public async signAsync() {
const orderHash = this.getOrderHash();
const signature = await promisify(web3.eth.sign)(this.params.maker, orderHash);
const {v, r, s} = ethUtil.fromRpcSig(signature);
this.params = _.assign(this.params, {
orderHashHex: orderHash,
v,
r: ethUtil.bufferToHex(r),
s: ethUtil.bufferToHex(s),
});
}
public createFill(shouldThrowOnInsufficientBalanceOrAllowance?: boolean, fillTakerTokenAmount?: BigNumber) {
const fill = {
orderAddresses: [
this.params.maker,
this.params.taker,
this.params.makerToken,
this.params.takerToken,
this.params.feeRecipient,
],
orderValues: [
this.params.makerTokenAmount,
this.params.takerTokenAmount,
this.params.makerFee,
this.params.takerFee,
this.params.expirationTimestampInSec,
this.params.salt,
],
fillTakerTokenAmount: fillTakerTokenAmount || this.params.takerTokenAmount,
shouldThrowOnInsufficientBalanceOrAllowance: !!shouldThrowOnInsufficientBalanceOrAllowance,
v: this.params.v,
r: this.params.r,
s: this.params.s,
};
return fill;
}
public createCancel(cancelTakerTokenAmount?: BigNumber) {
const cancel = {
orderAddresses: [
this.params.maker,
this.params.taker,
this.params.makerToken,
this.params.takerToken,
this.params.feeRecipient,
],
orderValues: [
this.params.makerTokenAmount,
this.params.takerTokenAmount,
this.params.makerFee,
this.params.takerFee,
this.params.expirationTimestampInSec,
this.params.salt,
],
cancelTakerTokenAmount: cancelTakerTokenAmount || this.params.takerTokenAmount,
};
return cancel;
}
private getOrderHash(): string {
const orderHash = crypto.solSHA3([
this.params.exchangeContractAddress,
this.params.maker,
this.params.taker,
this.params.makerToken,
this.params.takerToken,
this.params.feeRecipient,
this.params.makerTokenAmount,
this.params.takerTokenAmount,
this.params.makerFee,
this.params.takerFee,
this.params.expirationTimestampInSec,
this.params.salt,
]);
const orderHashHex = ethUtil.bufferToHex(orderHash);
return orderHashHex;
}
}

View File

@@ -0,0 +1,24 @@
import {ZeroEx} from '0x.js';
import {BigNumber} from 'bignumber.js';
import * as _ from 'lodash';
import {Order} from './order';
import {DefaultOrderParams, OptionalOrderParams, OrderParams} from './types';
export class OrderFactory {
private defaultOrderParams: DefaultOrderParams;
constructor(defaultOrderParams: DefaultOrderParams) {
this.defaultOrderParams = defaultOrderParams;
}
public async newSignedOrderAsync(customOrderParams: OptionalOrderParams = {}) {
const randomExpiration = new BigNumber(Math.floor((Date.now() + (Math.random() * 100000000000)) / 1000));
const orderParams: OrderParams = _.assign({}, {
expirationTimestampInSec: randomExpiration,
salt: ZeroEx.generatePseudoRandomSalt(),
taker: ZeroEx.NULL_ADDRESS,
}, this.defaultOrderParams, customOrderParams);
const order = new Order(orderParams);
await order.signAsync();
return order;
}
}

Some files were not shown because too many files have changed in this diff Show More